diff --git a/controllers/login_handler.go b/controllers/login_handler.go index 1508003..05a066f 100644 --- a/controllers/login_handler.go +++ b/controllers/login_handler.go @@ -26,12 +26,12 @@ func NewLoginHandler(r gin.IRouter, us entities.LoginUseCase, secretKey []byte) } func (l *LoginHandler) Login(c *gin.Context) { - // username and password are in the json body + // username is in the json body var u entities.LoginPayload if err := c.ShouldBindJSON(&u); err != nil { // Use type assertion to check if err is of type validator.ValidationErrors if _, ok := err.(validator.ValidationErrors); ok { - c.JSON(http.StatusBadRequest, gin.H{"error": "Username or password must be a non-empty string!"}) + c.JSON(http.StatusBadRequest, gin.H{"error": "Username must be a non-empty string!"}) return // exit on first error } else { // Handle other types of errors (e.g., JSON binding errors) diff --git a/controllers/prescription_handler.go b/controllers/prescription_handler.go index 68f8c40..671ae20 100644 --- a/controllers/prescription_handler.go +++ b/controllers/prescription_handler.go @@ -9,6 +9,7 @@ import ( "sothea-backend/controllers/middleware" "sothea-backend/entities" + "sothea-backend/repository/postgres" db "sothea-backend/repository/sqlc" ) @@ -187,6 +188,16 @@ func (h *PrescriptionHandler) AddLine(c *gin.Context) { ctx := c.Request.Context() created, err := h.Usecase.AddLine(ctx, &line) if err != nil { + if stockErr, ok := err.(*postgres.InsufficientStockError); ok { + c.JSON(http.StatusBadRequest, gin.H{ + "error": stockErr.Error(), + "code": stockErr.Code(), + "drug_id": stockErr.DrugID, + "total_required": stockErr.TotalRequired, + "total_available": stockErr.TotalAvailable, + }) + return + } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -224,6 +235,16 @@ func (h *PrescriptionHandler) UpdateLine(c *gin.Context) { ctx := c.Request.Context() updated, err := h.Usecase.UpdateLine(ctx, &line) if err != nil { + if stockErr, ok := err.(*postgres.InsufficientStockError); ok { + c.JSON(http.StatusBadRequest, gin.H{ + "error": stockErr.Error(), + "code": stockErr.Code(), + "drug_id": stockErr.DrugID, + "total_required": stockErr.TotalRequired, + "total_available": stockErr.TotalAvailable, + }) + return + } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -286,6 +307,16 @@ func (h *PrescriptionHandler) SetLineAllocations(c *gin.Context) { ctx := c.Request.Context() out, err := h.Usecase.SetLineAllocations(ctx, lineID, allocs) if err != nil { + if stockErr, ok := err.(*postgres.InsufficientStockError); ok { + c.JSON(http.StatusBadRequest, gin.H{ + "error": stockErr.Error(), + "code": stockErr.Code(), + "drug_id": stockErr.DrugID, + "total_required": stockErr.TotalRequired, + "total_available": stockErr.TotalAvailable, + }) + return + } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } diff --git a/db/queries/prescriptions.sql b/db/queries/prescriptions.sql index f035fe3..4d000ba 100644 --- a/db/queries/prescriptions.sql +++ b/db/queries/prescriptions.sql @@ -7,12 +7,14 @@ RETURNING id, created_at, updated_at; -- name: GetPrescriptionHeader :one SELECT p.id, p.patient_id, p.vid, p.notes, - p.created_by, p.created_at, p.updated_at, + p.created_by, p.created_at, p.updated_at, p.updated_by, p.is_dispensed, p.dispensed_by, p.dispensed_at, uc.name AS creator_name, + uu.name AS updater_name, ud.name AS dispenser_name FROM prescriptions p LEFT JOIN users uc ON uc.id = p.created_by +LEFT JOIN users uu ON uu.id = p.updated_by LEFT JOIN users ud ON ud.id = p.dispensed_by WHERE p.id = $1; @@ -23,8 +25,9 @@ SELECT pl.frequency_code, pl.duration, pl.duration_unit, pl.total_to_dispense, pl.is_packed, pl.packed_by, pl.packed_at, - u1.name AS packer_name, - u2.name AS updater_name, + u_packer.name AS packer_name, + u_updater.name AS updater_name, + u_creator.name AS creator_name, d.generic_name AS drug_name, d.route_code AS route_code, d.dispense_unit AS dispense_unit, @@ -36,8 +39,9 @@ SELECT END AS display_strength FROM prescription_lines pl LEFT JOIN drugs d ON d.id = pl.drug_id -LEFT JOIN users u1 ON u1.id = pl.packed_by -LEFT JOIN users u2 ON u2.id = pl.updated_by +LEFT JOIN users u_packer ON u_packer.id = pl.packed_by +LEFT JOIN users u_updater ON u_updater.id = pl.updated_by +LEFT JOIN users u_creator ON u_creator.id = pl.created_by WHERE pl.prescription_id = $1 ORDER BY pl.id; diff --git a/db/queries/users.sql b/db/queries/users.sql index 710f3eb..dcbc382 100644 --- a/db/queries/users.sql +++ b/db/queries/users.sql @@ -1,9 +1,9 @@ -- name: GetUserByUsername :one -SELECT id, username, password_hash +SELECT id, username FROM users WHERE username = $1; -- name: GetUserByID :one -SELECT id, username, password_hash +SELECT id, username FROM users WHERE id = $1; diff --git a/db/schema/patients.sql b/db/schema/patients.sql index 88ca397..f878333 100644 --- a/db/schema/patients.sql +++ b/db/schema/patients.sql @@ -8,7 +8,12 @@ CREATE TABLE patient_details gender VARCHAR(1) NOT NULL, village TEXT NOT NULL, contact_no TEXT NOT NULL, - drug_allergies TEXT + drug_allergies TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id) ); CREATE TABLE admin @@ -20,6 +25,12 @@ CREATE TABLE admin pregnant BOOLEAN NOT NULL, last_menstrual_period DATE, sent_to_id BOOLEAN NOT NULL, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid) -- Composite primary key ); @@ -43,6 +54,12 @@ CREATE TABLE past_medical_history sexually_transmitted_disease BOOLEAN, -- Allow NULL for 'Nil' option specified_stds TEXT, others TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -57,6 +74,12 @@ CREATE TABLE social_history cigarettes_per_day INTEGER, alcohol_history BOOLEAN NOT NULL, how_regular VARCHAR(1), + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -78,6 +101,12 @@ CREATE TABLE vital_statistics avg_hr NUMERIC(5, 1) NOT NULL, rand_blood_glucose_mmol_l NUMERIC(5, 1), icope_high_bp BOOLEAN, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -95,6 +124,12 @@ CREATE TABLE height_and_weight icope_lost_weight_past_months BOOLEAN, icope_no_desire_to_eat BOOLEAN, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -112,6 +147,11 @@ CREATE TABLE visual_acuity icope_eye_problem BOOLEAN, icope_treated_for_diabetes_or_bp BOOLEAN, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -130,6 +170,11 @@ CREATE TABLE dental icope_pain_in_mouth BOOLEAN, dental_notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -146,6 +191,11 @@ CREATE TABLE fall_risk fall_risk_score INTEGER NOT NULL, icope_complete_chair_stands BOOLEAN, icope_chair_stands_time BOOLEAN, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin @@ -170,6 +220,12 @@ CREATE TABLE doctors_consultation referral_needed BOOLEAN NOT NULL, referral_loc TEXT, remarks TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) -- Foreign key referencing the composite key in admin ); @@ -183,6 +239,12 @@ CREATE TABLE physiotherapy objective_assessment TEXT, -- Objective Assessment (Open Ended) intervention TEXT, -- Intervention (Open Ended) evaluation TEXT, -- Evaluation (Open Ended) + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) -- Foreign key referencing the composite key in admin -); \ No newline at end of file +); diff --git a/db/schema/users.sql b/db/schema/users.sql index 9c489a9..67b981e 100644 --- a/db/schema/users.sql +++ b/db/schema/users.sql @@ -1,11 +1,10 @@ /******************* - Add usernames and passwords + Add usernames */ CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, username TEXT NOT NULL UNIQUE, name TEXT, - password_hash TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); diff --git a/entities/models.go b/entities/models.go index cb3b672..c38f98b 100644 --- a/entities/models.go +++ b/entities/models.go @@ -77,11 +77,15 @@ type DrugStock struct { type Prescription struct { db.Prescription - Lines []PrescriptionLine `json:"lines"` + DispenserName *string `json:"dispenser_name"` + Lines []PrescriptionLine `json:"lines"` } type PrescriptionLine struct { db.PrescriptionLine + PackerName *string `json:"packer_name"` + UpdaterName *string `json:"updater_name"` + CreatorName *string `json:"creator_name"` Allocations []db.PrescriptionBatchItem `json:"allocations"` DispenseUnit string `json:"dispense_unit,omitempty"` } @@ -106,7 +110,6 @@ type SetAllocReq struct { type LoginPayload struct { Username string `json:"username" binding:"required"` - Password string `json:"password" binding:"required"` } // ---------------- Interfaces ---------------- diff --git a/repository/postgres/postgres_patient.go b/repository/postgres/postgres_patient.go index ab70078..5f108a0 100644 --- a/repository/postgres/postgres_patient.go +++ b/repository/postgres/postgres_patient.go @@ -472,9 +472,8 @@ func (p *postgresPatientRepository) GetDBUser(ctx context.Context, username stri return nil, err } return &db.User{ - ID: row.ID, - Username: row.Username, - PasswordHash: row.PasswordHash, + ID: row.ID, + Username: row.Username, }, nil } diff --git a/repository/postgres/postgres_prescription_errors.go b/repository/postgres/postgres_prescription_errors.go index 0882c86..ccfadfe 100644 --- a/repository/postgres/postgres_prescription_errors.go +++ b/repository/postgres/postgres_prescription_errors.go @@ -5,11 +5,11 @@ import ( "errors" "fmt" - "github.com/lib/pq" + "github.com/jackc/pgx/v5/pgconn" ) type InsufficientStockError struct { - PresentationID int64 `json:"presentation+id"` + DrugID int64 `json:"drug_id"` TotalRequired int64 `json:"total_required"` TotalAvailable int64 `json:"total_available"` } @@ -20,14 +20,17 @@ func (e *InsufficientStockError) Error() string { func (e *InsufficientStockError) Code() string { return "INSUFFICIENT_STOCK" } func mapPrescriptionSQLError(err error) error { - var pe *pq.Error + var pe *pgconn.PgError if !errors.As(err, &pe) { return err } // 23514 = check_violation; you raised with CONSTRAINT 'ck_insufficient_stock' - if string(pe.Code) == "23514" && pe.Constraint == "ck_insufficient_stock" { + if pe.Code == "23514" && pe.ConstraintName == "ck_insufficient_stock" { var d InsufficientStockError - _ = json.Unmarshal([]byte(pe.Detail), &d) + if err := json.Unmarshal([]byte(pe.Detail), &d); err != nil { + // If unmarshaling fails, return the original error with detail + return fmt.Errorf("insufficient stock: %s", pe.Detail) + } return &d } return err diff --git a/repository/postgres/postgres_prescriptions.go b/repository/postgres/postgres_prescriptions.go index 6c16833..e0dd4c8 100644 --- a/repository/postgres/postgres_prescriptions.go +++ b/repository/postgres/postgres_prescriptions.go @@ -105,6 +105,7 @@ func (r *postgresPrescriptionRepository) GetPrescriptionByID(ctx context.Context DispensedBy: header.DispensedBy, DispensedAt: header.DispensedAt, }, + DispenserName: header.DispenserName, } lineRows, err := q.ListPrescriptionLines(ctx, id) @@ -131,6 +132,9 @@ func (r *postgresPrescriptionRepository) GetPrescriptionByID(ctx context.Context PackedBy: row.PackedBy, PackedAt: row.PackedAt, }, + PackerName: row.PackerName, + UpdaterName: row.UpdaterName, + CreatorName: row.CreatorName, } lines = append(lines, l) lineIDs = append(lineIDs, row.ID) diff --git a/repository/sqlc/db.go b/repository/sqlc/db.go index b4a3b78..9d485b5 100644 --- a/repository/sqlc/db.go +++ b/repository/sqlc/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 package db diff --git a/repository/sqlc/models.go b/repository/sqlc/models.go index 63dec70..837f140 100644 --- a/repository/sqlc/models.go +++ b/repository/sqlc/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 package db @@ -268,12 +268,11 @@ type Unit struct { } type User struct { - ID int64 `json:"id"` - Username string `json:"username"` - Name *string `json:"name"` - PasswordHash string `json:"password_hash"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID int64 `json:"id"` + Username string `json:"username"` + Name *string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type VisualAcuity struct { diff --git a/repository/sqlc/patient.sql.go b/repository/sqlc/patient.sql.go index 3dc2778..b4fab9b 100644 --- a/repository/sqlc/patient.sql.go +++ b/repository/sqlc/patient.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 // source: patient.sql package db diff --git a/repository/sqlc/pharmacy.sql.go b/repository/sqlc/pharmacy.sql.go index fbb7a85..e49f985 100644 --- a/repository/sqlc/pharmacy.sql.go +++ b/repository/sqlc/pharmacy.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 // source: pharmacy.sql package db diff --git a/repository/sqlc/prescriptions.sql.go b/repository/sqlc/prescriptions.sql.go index c75a131..b0ef2d5 100644 --- a/repository/sqlc/prescriptions.sql.go +++ b/repository/sqlc/prescriptions.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 // source: prescriptions.sql package db @@ -196,12 +196,14 @@ func (q *Queries) GetPrescriptionDispensed(ctx context.Context, id int64) (bool, const getPrescriptionHeader = `-- name: GetPrescriptionHeader :one SELECT p.id, p.patient_id, p.vid, p.notes, - p.created_by, p.created_at, p.updated_at, + p.created_by, p.created_at, p.updated_at, p.updated_by, p.is_dispensed, p.dispensed_by, p.dispensed_at, uc.name AS creator_name, + uu.name AS updater_name, ud.name AS dispenser_name FROM prescriptions p LEFT JOIN users uc ON uc.id = p.created_by +LEFT JOIN users uu ON uu.id = p.updated_by LEFT JOIN users ud ON ud.id = p.dispensed_by WHERE p.id = $1 ` @@ -214,10 +216,12 @@ type GetPrescriptionHeaderRow struct { CreatedBy *int64 `json:"created_by"` CreatedAt time.Time `json:"created_at"` UpdatedAt *time.Time `json:"updated_at"` + UpdatedBy *int64 `json:"updated_by"` IsDispensed bool `json:"is_dispensed"` DispensedBy *int64 `json:"dispensed_by"` DispensedAt *time.Time `json:"dispensed_at"` CreatorName *string `json:"creator_name"` + UpdaterName *string `json:"updater_name"` DispenserName *string `json:"dispenser_name"` } @@ -232,10 +236,12 @@ func (q *Queries) GetPrescriptionHeader(ctx context.Context, id int64) (GetPresc &i.CreatedBy, &i.CreatedAt, &i.UpdatedAt, + &i.UpdatedBy, &i.IsDispensed, &i.DispensedBy, &i.DispensedAt, &i.CreatorName, + &i.UpdaterName, &i.DispenserName, ) return i, err @@ -448,8 +454,9 @@ SELECT pl.frequency_code, pl.duration, pl.duration_unit, pl.total_to_dispense, pl.is_packed, pl.packed_by, pl.packed_at, - u1.name AS packer_name, - u2.name AS updater_name, + u_packer.name AS packer_name, + u_updater.name AS updater_name, + u_creator.name AS creator_name, d.generic_name AS drug_name, d.route_code AS route_code, d.dispense_unit AS dispense_unit, @@ -461,8 +468,9 @@ SELECT END AS display_strength FROM prescription_lines pl LEFT JOIN drugs d ON d.id = pl.drug_id -LEFT JOIN users u1 ON u1.id = pl.packed_by -LEFT JOIN users u2 ON u2.id = pl.updated_by +LEFT JOIN users u_packer ON u_packer.id = pl.packed_by +LEFT JOIN users u_updater ON u_updater.id = pl.updated_by +LEFT JOIN users u_creator ON u_creator.id = pl.created_by WHERE pl.prescription_id = $1 ORDER BY pl.id ` @@ -484,6 +492,7 @@ type ListPrescriptionLinesRow struct { PackedAt *time.Time `json:"packed_at"` PackerName *string `json:"packer_name"` UpdaterName *string `json:"updater_name"` + CreatorName *string `json:"creator_name"` DrugName *string `json:"drug_name"` RouteCode *string `json:"route_code"` DispenseUnit *string `json:"dispense_unit"` @@ -516,6 +525,7 @@ func (q *Queries) ListPrescriptionLines(ctx context.Context, prescriptionID int6 &i.PackedAt, &i.PackerName, &i.UpdaterName, + &i.CreatorName, &i.DrugName, &i.RouteCode, &i.DispenseUnit, diff --git a/repository/sqlc/users.sql.go b/repository/sqlc/users.sql.go index 0c63f07..f6693c6 100644 --- a/repository/sqlc/users.sql.go +++ b/repository/sqlc/users.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 // source: users.sql package db @@ -10,39 +10,37 @@ import ( ) const getUserByID = `-- name: GetUserByID :one -SELECT id, username, password_hash +SELECT id, username FROM users WHERE id = $1 ` type GetUserByIDRow struct { - ID int64 `json:"id"` - Username string `json:"username"` - PasswordHash string `json:"password_hash"` + ID int64 `json:"id"` + Username string `json:"username"` } func (q *Queries) GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, error) { row := q.db.QueryRow(ctx, getUserByID, id) var i GetUserByIDRow - err := row.Scan(&i.ID, &i.Username, &i.PasswordHash) + err := row.Scan(&i.ID, &i.Username) return i, err } const getUserByUsername = `-- name: GetUserByUsername :one -SELECT id, username, password_hash +SELECT id, username FROM users WHERE username = $1 ` type GetUserByUsernameRow struct { - ID int64 `json:"id"` - Username string `json:"username"` - PasswordHash string `json:"password_hash"` + ID int64 `json:"id"` + Username string `json:"username"` } func (q *Queries) GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error) { row := q.db.QueryRow(ctx, getUserByUsername, username) var i GetUserByUsernameRow - err := row.Scan(&i.ID, &i.Username, &i.PasswordHash) + err := row.Scan(&i.ID, &i.Username) return i, err } diff --git a/sql/1_users_setup.sql b/sql/1_users_setup.sql index 3182144..d34dea5 100644 --- a/sql/1_users_setup.sql +++ b/sql/1_users_setup.sql @@ -4,22 +4,38 @@ DROP TABLE IF EXISTS users; /******************* - Add usernames and passwords + Add usernames */ CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, username TEXT NOT NULL UNIQUE, name TEXT, - password_hash TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- Insert the users -INSERT INTO users (username, name, password_hash) -VALUES ('admin', 'admin', '$2a$10$Y/FMxVZsuiPcVutd0O3kfuWEyWyqZUb4HC5yXF7.xVxNCt9GUfOPe'); -INSERT INTO users (username, name, password_hash) -VALUES ('user', 'user', '$2a$10$cqFdRzsZVwGF6fI4N.YEYOOEZL7B/93RmEZRkVPVHHqyBMfNwy48i'); +INSERT INTO users (username, name) +VALUES + ('admin', 'admin'), + ('user', 'user'), + ('reg', 'Reg'), + ('reg_id', 'Reg (ID)'), + ('va', 'VA'), + ('fall_risk', 'Fall Risk'), + ('dental', 'Dental'), + ('physiotherapy', 'Physiotherapy'), + ('pharmacy_packer', 'Pharmacy packer'), + ('pharmacy_dispenser', 'Pharmacy dispenser'), + ('dr_ron', 'Dr Ron'), + ('dr_joseph', 'Dr Joseph'), + ('dr_hh', 'Dr HH'), + ('dr_kevan', 'Dr Kevan'), + ('dr_sherryl', 'Dr Sherryl'), + ('dr_jonathan', 'Dr Jonathan'), + ('dr_lauren', 'Dr Lauren'), + ('dr_celeste', 'Dr Celeste'), + ('dr_barbara', 'Dr Barbara'); -------------------------------------------------------------------------------- -- GLOBAL AUDIT HELPERS (used by ALL other tables; keep them in this file) diff --git a/sql/2_patients_setup.sql b/sql/2_patients_setup.sql index f3ad864..8f00129 100644 --- a/sql/2_patients_setup.sql +++ b/sql/2_patients_setup.sql @@ -25,7 +25,12 @@ CREATE TABLE IF NOT EXISTS patient_details gender VARCHAR(1) NOT NULL, village TEXT NOT NULL, contact_no TEXT NOT NULL, - drug_allergies TEXT + drug_allergies TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id) ); CREATE TABLE IF NOT EXISTS admin @@ -37,6 +42,12 @@ CREATE TABLE IF NOT EXISTS admin pregnant BOOLEAN NOT NULL, last_menstrual_period DATE, sent_to_id BOOLEAN NOT NULL, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid) -- Composite primary key ); @@ -60,6 +71,12 @@ CREATE TABLE IF NOT EXISTS past_medical_history sexually_transmitted_disease BOOLEAN, -- Allow NULL for 'Nil' option specified_stds TEXT, others TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -74,6 +91,12 @@ CREATE TABLE IF NOT EXISTS social_history cigarettes_per_day INTEGER, alcohol_history BOOLEAN NOT NULL, how_regular VARCHAR(1), + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -95,6 +118,12 @@ CREATE TABLE IF NOT EXISTS vital_statistics avg_hr NUMERIC(5, 1) NOT NULL, rand_blood_glucose_mmol_l NUMERIC(5, 1), icope_high_bp BOOLEAN, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -112,6 +141,12 @@ CREATE TABLE IF NOT EXISTS height_and_weight icope_lost_weight_past_months BOOLEAN, icope_no_desire_to_eat BOOLEAN, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -129,6 +164,11 @@ CREATE TABLE IF NOT EXISTS visual_acuity icope_eye_problem BOOLEAN, icope_treated_for_diabetes_or_bp BOOLEAN, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -147,6 +187,11 @@ CREATE TABLE IF NOT EXISTS dental icope_pain_in_mouth BOOLEAN, dental_notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin ); @@ -163,6 +208,11 @@ CREATE TABLE IF NOT EXISTS fall_risk fall_risk_score INTEGER NOT NULL, icope_complete_chair_stands BOOLEAN, icope_chair_stands_time BOOLEAN, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) ON DELETE CASCADE -- Foreign key referencing the composite key in admin @@ -187,6 +237,12 @@ CREATE TABLE IF NOT EXISTS doctors_consultation referral_needed BOOLEAN NOT NULL, referral_loc TEXT, remarks TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) -- Foreign key referencing the composite key in admin ); @@ -200,10 +256,108 @@ CREATE TABLE IF NOT EXISTS physiotherapy objective_assessment TEXT, -- Objective Assessment (Open Ended) intervention TEXT, -- Intervention (Open Ended) evaluation TEXT, -- Evaluation (Open Ended) + + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + created_by BIGINT REFERENCES users(id), + updated_at TIMESTAMPTZ, + updated_by BIGINT REFERENCES users(id), + PRIMARY KEY (id, vid), -- Composite primary key CONSTRAINT fk_admin FOREIGN KEY (id, vid) REFERENCES admin (id, vid) -- Foreign key referencing the composite key in admin ); +/******************* + Create audit triggers for all tables +********************/ + +CREATE TRIGGER trg_patient_details_audit +BEFORE INSERT OR UPDATE ON patient_details +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_patient_details_log +AFTER INSERT OR UPDATE OR DELETE ON patient_details +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_admin_audit +BEFORE INSERT OR UPDATE ON admin +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_admin_log +AFTER INSERT OR UPDATE OR DELETE ON admin +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_past_medical_history_audit +BEFORE INSERT OR UPDATE ON past_medical_history +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_past_medical_history_log +AFTER INSERT OR UPDATE OR DELETE ON past_medical_history +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_social_history_audit +BEFORE INSERT OR UPDATE ON social_history +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_social_history_log +AFTER INSERT OR UPDATE OR DELETE ON social_history +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_vital_statistics_audit +BEFORE INSERT OR UPDATE ON vital_statistics +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_vital_statistics_log +AFTER INSERT OR UPDATE OR DELETE ON vital_statistics +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_height_and_weight_audit +BEFORE INSERT OR UPDATE ON height_and_weight +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_height_and_weight_log +AFTER INSERT OR UPDATE OR DELETE ON height_and_weight +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_visual_acuity_audit +BEFORE INSERT OR UPDATE ON visual_acuity +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_visual_acuity_log +AFTER INSERT OR UPDATE OR DELETE ON visual_acuity +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_dental_audit +BEFORE INSERT OR UPDATE ON dental +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_dental_log +AFTER INSERT OR UPDATE OR DELETE ON dental +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_fall_risk_audit +BEFORE INSERT OR UPDATE ON fall_risk +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_fall_risk_log +AFTER INSERT OR UPDATE OR DELETE ON fall_risk +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_doctors_consultation_audit +BEFORE INSERT OR UPDATE ON doctors_consultation +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_doctors_consultation_log +AFTER INSERT OR UPDATE OR DELETE ON doctors_consultation +FOR EACH ROW EXECUTE FUNCTION audit_row(); + +CREATE TRIGGER trg_physiotherapy_audit +BEFORE INSERT OR UPDATE ON physiotherapy +FOR EACH ROW EXECUTE FUNCTION set_audit_fields(); + +CREATE TRIGGER trg_physiotherapy_log +AFTER INSERT OR UPDATE OR DELETE ON physiotherapy +FOR EACH ROW EXECUTE FUNCTION audit_row(); + -- Auto-increment VID per patient CREATE OR REPLACE FUNCTION set_entry_id() RETURNS TRIGGER AS $$ diff --git a/sql/4_prescription_setup.sql b/sql/4_prescription_setup.sql index 0188119..2d6fe0f 100644 --- a/sql/4_prescription_setup.sql +++ b/sql/4_prescription_setup.sql @@ -196,13 +196,13 @@ BEGIN WHERE b.drug_id = p_drug_id; IF available < need_extra THEN - RAISE EXCEPTION 'insufficient stock: total needed %, available %', p_required, available + need_extra + RAISE EXCEPTION 'insufficient stock: total needed %, available %', p_required, available USING ERRCODE = '23514', -- check_violation CONSTRAINT = 'ck_insufficient_stock', DETAIL = json_build_object( 'drug_id', p_drug_id, 'total_required', p_required, - 'total_available', available + allocated + 'total_available', available )::text; END IF; END; diff --git a/usecases/login_ucase.go b/usecases/login_ucase.go index 6d6a075..eed6001 100644 --- a/usecases/login_ucase.go +++ b/usecases/login_ucase.go @@ -6,8 +6,6 @@ import ( "sothea-backend/controllers/middleware" "sothea-backend/entities" - - "golang.org/x/crypto/bcrypt" ) type loginUsecase struct { @@ -34,11 +32,6 @@ func (l *loginUsecase) Login(ctx context.Context, user entities.LoginPayload) (s return "", err } - err = bcrypt.CompareHashAndPassword([]byte(dbUser.PasswordHash), []byte(user.Password)) - if err != nil { - return "", entities.ErrLoginFailed - } - token, err := middleware.CreateToken(dbUser.ID, user.Username, l.secretKey) if err != nil { return "", err