From 81787496e5c2e16df4cd4815606f127c40bd1153 Mon Sep 17 00:00:00 2001 From: Rajiv Harlalka Date: Mon, 23 Sep 2024 12:06:03 +0530 Subject: [PATCH 1/2] feat:add paper approving endpoint Signed-off-by: Rajiv Harlalka --- backend/handlers.go | 54 +++++++++++++++++++++++++++++++++++++ backend/main.go | 1 + backend/pkg/db/query.go | 39 +++++++++++++++++++++++++++ backend/pkg/models/model.go | 8 +++--- backend/pkg/utils/util.go | 44 ++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 backend/pkg/utils/util.go diff --git a/backend/handlers.go b/backend/handlers.go index da86846e..acab0b81 100644 --- a/backend/handlers.go +++ b/backend/handlers.go @@ -19,6 +19,7 @@ import ( "github.com/metakgp/iqps/backend/pkg/config" "github.com/metakgp/iqps/backend/pkg/db" "github.com/metakgp/iqps/backend/pkg/models" + "github.com/metakgp/iqps/backend/pkg/utils" "github.com/metakgp/iqps/backend/query" ) @@ -57,6 +58,59 @@ func HandleQPYear(w http.ResponseWriter, r *http.Request) { sendResponse(w, http.StatusOK, map[string]int{"min": minYear, "max": maxYear}) } +func HandleApprovePaper(w http.ResponseWriter, r *http.Request) { + approverUsername := r.Context().Value(CLAIMS_KEY).(*Claims).Username + + db := db.GetDB() + var qpDetails models.QuestionPaper + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&qpDetails); err != nil { + sendErrorResponse(w, http.StatusInternalServerError, "Could not find Question Paper, Try Later!", nil) + config.Get().Logger.Errorf("HandleApprovePaper: could not approve paper, invalid Body: %+v", err.Error()) + return + } + + destFileLink := fmt.Sprintf("peqp/qp/%s_%s_%v_%v_%v.pdf", qpDetails.CourseCode, qpDetails.CourseName, qpDetails.Year, qpDetails.Semester, qpDetails.Exam) + srcFile := filepath.Join(config.Get().StaticFilesStorageLocation, config.Get().UploadedQPsPath, qpDetails.FileLink) + destFile := utils.SanitizeFileLink(filepath.Join(config.Get().StaticFilesStorageLocation, destFileLink)) + + err := utils.CopyFile(srcFile, destFile) + if err != nil { + sendErrorResponse(w, http.StatusInternalServerError, "Could not move Question Paper, Try Later!", nil) + config.Get().Logger.Errorf("HandleApprovePaper: could not approve paper, could not move question paper: %+v", err.Error()) + return + } + + newQPDetails := models.QuestionPaper{ + CourseCode: qpDetails.CourseCode, + CourseName: qpDetails.CourseName, + Year: qpDetails.Year, + Exam: qpDetails.Exam, + FileLink: destFileLink, + ApproveStatus: true, + Semester: qpDetails.Semester, + FromLibrary: false, + ApprovedBy: approverUsername, + } + + err = db.InsertNewPaper(&newQPDetails) + if err != nil { + // undelete file + sendErrorResponse(w, http.StatusInternalServerError, "could not update file details, try again later", nil) + config.Get().Logger.Errorf("HandleApprovePaper: Could not approve paper: %+v", err.Error()) + return + } + + err = db.MarkPaperAsSoftDeletedAndUnApprove(qpDetails.ID) + if err != nil { + utils.DeleteFile(destFile) + sendErrorResponse(w, http.StatusInternalServerError, "error updating paper details!", nil) + config.Get().Logger.Errorf("HandleApprovePaper: error soft-deleting paper: %+v PaperDetails: %d", err.Error(), qpDetails.ID) + return + } + sendResponse(w, http.StatusOK, httpResp{Message: "File Approved successfully"}) +} + func HandleLibraryPapers(w http.ResponseWriter, r *http.Request) { db := db.GetDB() rows, err := db.Db.Query(context.Background(), "SELECT id, course_code, course_name, year, exam, filelink, from_library FROM iqps WHERE from_library = 'true'") diff --git a/backend/main.go b/backend/main.go index 7e12c5ee..4461e0bf 100644 --- a/backend/main.go +++ b/backend/main.go @@ -25,6 +25,7 @@ func main() { http.HandleFunc("POST /oauth", GhAuth) http.Handle("GET /unapproved", JWTMiddleware(http.HandlerFunc(ListUnapprovedPapers))) http.Handle("GET /all", JWTMiddleware(http.HandlerFunc(ListAllPapers))) + http.Handle("POST /approve", JWTMiddleware(http.HandlerFunc(HandleApprovePaper))) logger := config.Get().Logger c := cors.New(cors.Options{ diff --git a/backend/pkg/db/query.go b/backend/pkg/db/query.go index c5827d8b..7573f9da 100644 --- a/backend/pkg/db/query.go +++ b/backend/pkg/db/query.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/jackc/pgx/v5" "github.com/metakgp/iqps/backend/pkg/config" "github.com/metakgp/iqps/backend/pkg/models" ) @@ -31,3 +32,41 @@ func (db *db) FetchAllQuestionPapers() ([]models.QuestionPaper, error) { return qps, nil } + +func (db *db) InsertNewPaper(qpDetails *models.QuestionPaper) error { + query := "INSERT INTO iqps (course_code, course_name, year, exam, filelink, semester, approve_status, from_library, approved_by) VALUES (@course_code, @course_name, @year, @exam, @filelink, @semester, @approve_status, @from_library, @approved_by)" + params := pgx.NamedArgs{ + "course_code": qpDetails.CourseCode, + "course_name": qpDetails.CourseName, + "year": qpDetails.Year, + "exam": qpDetails.Exam, + "semester": qpDetails.Semester, + "filelink": qpDetails.FileLink, + "from_library": qpDetails.FromLibrary, + "approve_status": qpDetails.ApproveStatus, + "approved_by": qpDetails.ApprovedBy, + } + + _, err := db.Db.Exec(context.Background(), query, params) + if err != nil { + return err + } + return nil +} + +func (db *db) MarkPaperAsSoftDeletedAndUnApprove(qpID int) error { + query := "UPDATE iqps set approve_status=false, is_deleted = true where id=@qpID and is_deleted=false" + params := pgx.NamedArgs{ + "qpID": qpID, + } + + ct, err := db.Db.Exec(context.Background(), query, params) + if err != nil { + return err + } + + if ct.RowsAffected() == 0 { + return fmt.Errorf("no such paper found to be approved") + } + return nil +} diff --git a/backend/pkg/models/model.go b/backend/pkg/models/model.go index 6a59f2d5..eba9a6c9 100644 --- a/backend/pkg/models/model.go +++ b/backend/pkg/models/model.go @@ -5,16 +5,18 @@ import ( ) type QuestionPaper struct { - ID int `json:"id"` + ID int `json:"id,string"` CourseCode string `json:"course_code"` CourseName string `json:"course_name"` - Year int `json:"year"` + Year int `json:"year,string"` Exam string `json:"exam"` FileLink string `json:"filelink"` FromLibrary bool `json:"from_library"` UploadTimestamp pgtype.Timestamp `json:"upload_timestamp,omitempty"` ApproveStatus bool `json:"approve_status"` - CourseDetails string `json:"course_details,omitempty"` + Semester string `json:"semester"` + IsDeleted bool `json:"is_deleted"` + ApprovedBy string `json:"approved_by"` } type UploadEndpointRes struct { diff --git a/backend/pkg/utils/util.go b/backend/pkg/utils/util.go new file mode 100644 index 00000000..1a44768b --- /dev/null +++ b/backend/pkg/utils/util.go @@ -0,0 +1,44 @@ +package utils + +import ( + "fmt" + "io" + "os" + "strings" +) + +func CopyFile(srcPath, destPath string) error { + inputFile, err := os.Open(srcPath) + if err != nil { + return fmt.Errorf("couldn't open source file: %v", err) + } + defer inputFile.Close() + + outputFile, err := os.Create(destPath) + if err != nil { + return fmt.Errorf("couldn't open dest file: %v", err) + } + defer outputFile.Close() + + _, err = io.Copy(outputFile, inputFile) + if err != nil { + return fmt.Errorf("couldn't copy to dest from source: %v", err) + } + + inputFile.Close() + return nil +} + +func DeleteFile(path string) error { + err := os.Remove(path) + if err != nil { + return err + } + return nil +} + +func SanitizeFileLink(path string) string { + result := strings.Replace(path, " ", "_", -1) + result = strings.TrimSpace(result) + return result +} From b2f36ccf01f44afcd1c27cf6890e2b449d38f90a Mon Sep 17 00:00:00 2001 From: Rajiv Harlalka Date: Tue, 24 Sep 2024 18:21:13 +0530 Subject: [PATCH 2/2] minor fixes Signed-off-by: Rajiv Harlalka --- backend/handlers.go | 9 ++++++--- backend/pkg/db/query.go | 12 ++++++------ backend/pkg/models/model.go | 4 ++-- backend/query/queries.go | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/backend/handlers.go b/backend/handlers.go index acab0b81..7fc610e4 100644 --- a/backend/handlers.go +++ b/backend/handlers.go @@ -71,7 +71,7 @@ func HandleApprovePaper(w http.ResponseWriter, r *http.Request) { } destFileLink := fmt.Sprintf("peqp/qp/%s_%s_%v_%v_%v.pdf", qpDetails.CourseCode, qpDetails.CourseName, qpDetails.Year, qpDetails.Semester, qpDetails.Exam) - srcFile := filepath.Join(config.Get().StaticFilesStorageLocation, config.Get().UploadedQPsPath, qpDetails.FileLink) + srcFile := filepath.Join(config.Get().StaticFilesStorageLocation, qpDetails.FileLink) destFile := utils.SanitizeFileLink(filepath.Join(config.Get().StaticFilesStorageLocation, destFileLink)) err := utils.CopyFile(srcFile, destFile) @@ -93,13 +93,16 @@ func HandleApprovePaper(w http.ResponseWriter, r *http.Request) { ApprovedBy: approverUsername, } - err = db.InsertNewPaper(&newQPDetails) + id, err := db.InsertNewPaper(&newQPDetails) if err != nil { // undelete file + utils.DeleteFile(destFile) sendErrorResponse(w, http.StatusInternalServerError, "could not update file details, try again later", nil) config.Get().Logger.Errorf("HandleApprovePaper: Could not approve paper: %+v", err.Error()) return } + // log line to help which entry was made by deleting which paper for recovery + config.Get().Logger.Infof("HandleApprovePaper: Id %d added against Id %d", id, qpDetails.ID) err = db.MarkPaperAsSoftDeletedAndUnApprove(qpDetails.ID) if err != nil { @@ -168,7 +171,7 @@ func HandleQPSearch(w http.ResponseWriter, r *http.Request) { var qps []models.QuestionPaper = make([]models.QuestionPaper, 0) for rows.Next() { qp := models.QuestionPaper{} - err := rows.Scan(&qp.ID, &qp.CourseCode, &qp.CourseName, &qp.Year, &qp.Exam, &qp.FileLink, &qp.FromLibrary, &qp.UploadTimestamp, &qp.ApproveStatus) + err := rows.Scan(&qp.ID, &qp.CourseCode, &qp.CourseName, &qp.Year, &qp.Exam, &qp.FileLink, &qp.FromLibrary, &qp.UploadTimestamp, &qp.ApproveStatus, &qp.Semester) if err != nil { config.Get().Logger.Error("HandleQPSearch: Error parsing question paper details") sendErrorResponse(w, http.StatusInternalServerError, err.Error(), nil) diff --git a/backend/pkg/db/query.go b/backend/pkg/db/query.go index 7573f9da..1fb83194 100644 --- a/backend/pkg/db/query.go +++ b/backend/pkg/db/query.go @@ -33,8 +33,8 @@ func (db *db) FetchAllQuestionPapers() ([]models.QuestionPaper, error) { return qps, nil } -func (db *db) InsertNewPaper(qpDetails *models.QuestionPaper) error { - query := "INSERT INTO iqps (course_code, course_name, year, exam, filelink, semester, approve_status, from_library, approved_by) VALUES (@course_code, @course_name, @year, @exam, @filelink, @semester, @approve_status, @from_library, @approved_by)" +func (db *db) InsertNewPaper(qpDetails *models.QuestionPaper) (int, error) { + query := "INSERT INTO iqps (course_code, course_name, year, exam, filelink, semester, approve_status, from_library, approved_by) VALUES (@course_code, @course_name, @year, @exam, @filelink, @semester, @approve_status, @from_library, @approved_by) RETURNING id" params := pgx.NamedArgs{ "course_code": qpDetails.CourseCode, "course_name": qpDetails.CourseName, @@ -46,12 +46,12 @@ func (db *db) InsertNewPaper(qpDetails *models.QuestionPaper) error { "approve_status": qpDetails.ApproveStatus, "approved_by": qpDetails.ApprovedBy, } - - _, err := db.Db.Exec(context.Background(), query, params) + var id int + err := db.Db.QueryRow(context.Background(), query, params).Scan(&id) if err != nil { - return err + return 0, err } - return nil + return id, nil } func (db *db) MarkPaperAsSoftDeletedAndUnApprove(qpID int) error { diff --git a/backend/pkg/models/model.go b/backend/pkg/models/model.go index eba9a6c9..aea7f309 100644 --- a/backend/pkg/models/model.go +++ b/backend/pkg/models/model.go @@ -15,8 +15,8 @@ type QuestionPaper struct { UploadTimestamp pgtype.Timestamp `json:"upload_timestamp,omitempty"` ApproveStatus bool `json:"approve_status"` Semester string `json:"semester"` - IsDeleted bool `json:"is_deleted"` - ApprovedBy string `json:"approved_by"` + IsDeleted bool `json:"is_deleted,omitempty"` + ApprovedBy string `json:"approved_by,omitempty"` } type UploadEndpointRes struct { diff --git a/backend/query/queries.go b/backend/query/queries.go index 45495501..d8f9548a 100644 --- a/backend/query/queries.go +++ b/backend/query/queries.go @@ -93,7 +93,7 @@ partial_search as ( limit 30 ), result as ( select - iqps.id,iqps.course_code, iqps.course_name, iqps.year, iqps.exam, iqps.filelink, iqps.from_library, iqps.upload_timestamp, iqps.approve_status + iqps.id,iqps.course_code, iqps.course_name, iqps.year, iqps.exam, iqps.filelink, iqps.from_library, iqps.upload_timestamp, iqps.approve_status, iqps.semester from fuzzy full outer join full_text on fuzzy.id = full_text.id