From c28f7a4038d8d9c06820d341c2c4eca22031511f Mon Sep 17 00:00:00 2001 From: Morall0 Date: Tue, 7 Oct 2025 19:45:56 -0600 Subject: [PATCH 1/5] Adding password hashing and storing the hash --- security/hash.go | 39 +++++++++++++++++++++++++++++++++++++++ store/sqlite.go | 19 ++++++++++++++++--- store/store.go | 2 +- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 security/hash.go diff --git a/security/hash.go b/security/hash.go new file mode 100644 index 0000000..46eb31a --- /dev/null +++ b/security/hash.go @@ -0,0 +1,39 @@ +package security + +import ( + "crypto/rand" + "golang.org/x/crypto/argon2" +) + +type Params struct { + Memory uint32 + Iterations uint32 + Parallelism uint8 + SaltLength uint32 + KeyLength uint32 +} + +func HashPassword(password string, p *Params) (hash []byte, err error) { + // Generate a cryptographically secure random salt. + salt, err := generateSalt(p.SaltLength) + if err != nil { + return nil, err + } + + // Pass the plaintext password, salt and parameters to the argon2.IDKey + // function. This will generate a hash of the password using the Argon2id + // variant. + hash = argon2.IDKey([]byte(password), salt, p.Iterations, p.Memory, p.Parallelism, p.KeyLength) + + return hash, nil +} + +func generateSalt( n uint32) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/store/sqlite.go b/store/sqlite.go index 0414336..c141195 100644 --- a/store/sqlite.go +++ b/store/sqlite.go @@ -1,8 +1,11 @@ package store import ( - "context" - "database/sql" + "context" + "database/sql" + "log" + + "lidsol.org/papeador/security" ) type SQLiteStore struct{ @@ -23,7 +26,17 @@ func (s *SQLiteStore) CreateUser(ctx context.Context, u *User) error { return err } - res, err := s.DB.ExecContext(ctx, "INSERT INTO user (username,passhash,email) VALUES (?, ?, ?)", u.Username, u.Passhash, u.Email) + // Initializing params for Argon2 + p := &security.Params{ + Memory: 64 * 1024, + Iterations: 3, + Parallelism: 2, + SaltLength: 16, + KeyLength: 32, + } + + passhash, err := security.HashPassword(u.Password, p) + res, err := s.DB.ExecContext(ctx, "INSERT INTO user (username,passhash,email) VALUES (?, ?, ?)", u.Username, passhash, u.Email) if err != nil { return err } diff --git a/store/store.go b/store/store.go index 85808ac..c571665 100644 --- a/store/store.go +++ b/store/store.go @@ -13,7 +13,7 @@ var ( type User struct { UserID int64 `json:"user_id"` Username string `json:"username"` - Passhash string `json:"passhash"` + Password string `json:"password"` Email string `json:"email"` } From 94bfc548296bc7c7503ccc26bf164080aac0afd2 Mon Sep 17 00:00:00 2001 From: Francisco-Galindo Date: Mon, 13 Oct 2025 19:43:54 -0600 Subject: [PATCH 2/5] store/sqlite.go: Indent with tabs --- manifest.scm | 2 +- store/sqlite.go | 112 ++++++++++++++++++++++++------------------------ 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/manifest.scm b/manifest.scm index c7b1882..d4bb5dc 100644 --- a/manifest.scm +++ b/manifest.scm @@ -1 +1 @@ -(specifications->manifest (list "sqlite" "go")) +(specifications->manifest (list "sqlite" "go" "podman" "pkg-config" "btrfs-progs-static" "gpgme" "go-github-com-containerd-btrfs-v2" "bat")) diff --git a/store/sqlite.go b/store/sqlite.go index c141195..778b943 100644 --- a/store/sqlite.go +++ b/store/sqlite.go @@ -9,79 +9,79 @@ import ( ) type SQLiteStore struct{ - DB *sql.DB + DB *sql.DB } func NewSQLiteStore(db *sql.DB) Store { - return &SQLiteStore{DB: db} + return &SQLiteStore{DB: db} } func (s *SQLiteStore) CreateUser(ctx context.Context, u *User) error { - // Verify unique username/email - var username string - err := s.DB.QueryRowContext(ctx, "SELECT username FROM user WHERE username=? OR email=?", u.Username, u.Email).Scan(&username) - if err == nil { - return ErrAlreadyExists - } else if err != sql.ErrNoRows { - return err - } + // Verify unique username/email + var username string + err := s.DB.QueryRowContext(ctx, "SELECT username FROM user WHERE username=? OR email=?", u.Username, u.Email).Scan(&username) + if err == nil { + return ErrAlreadyExists + } else if err != sql.ErrNoRows { + return err + } // Initializing params for Argon2 p := &security.Params{ - Memory: 64 * 1024, - Iterations: 3, - Parallelism: 2, - SaltLength: 16, - KeyLength: 32, - } + Memory: 64 * 1024, + Iterations: 3, + Parallelism: 2, + SaltLength: 16, + KeyLength: 32, + } passhash, err := security.HashPassword(u.Password, p) - res, err := s.DB.ExecContext(ctx, "INSERT INTO user (username,passhash,email) VALUES (?, ?, ?)", u.Username, passhash, u.Email) - if err != nil { - return err - } - if id, ierr := res.LastInsertId(); ierr == nil { - u.UserID = id - } - return nil + res, err := s.DB.ExecContext(ctx, "INSERT INTO user (username,passhash,email) VALUES (?, ?, ?)", u.Username, passhash, u.Email) + if err != nil { + return err + } + if id, ierr := res.LastInsertId(); ierr == nil { + u.UserID = id + } + return nil } func (s *SQLiteStore) CreateContest(ctx context.Context, c *Contest) error { - var name string - err := s.DB.QueryRowContext(ctx, "SELECT contest_name FROM contest WHERE contest_name=?", c.ContestName).Scan(&name) - if err == nil { - return ErrAlreadyExists - } else if err != sql.ErrNoRows { - return err - } + var name string + err := s.DB.QueryRowContext(ctx, "SELECT contest_name FROM contest WHERE contest_name=?", c.ContestName).Scan(&name) + if err == nil { + return ErrAlreadyExists + } else if err != sql.ErrNoRows { + return err + } - res, err := s.DB.ExecContext(ctx, "INSERT INTO contest (contest_name) VALUES (?)", c.ContestName) - if err != nil { - return err - } - if id, ierr := res.LastInsertId(); ierr == nil { - c.ContestID = id - } - return nil + res, err := s.DB.ExecContext(ctx, "INSERT INTO contest (contest_name) VALUES (?)", c.ContestName) + if err != nil { + return err + } + if id, ierr := res.LastInsertId(); ierr == nil { + c.ContestID = id + } + return nil } func (s *SQLiteStore) CreateProblem(ctx context.Context, p *Problem) error { - if p.ContestID != nil { - var cid int64 - err := s.DB.QueryRowContext(ctx, "SELECT contest_id FROM contest WHERE contest_id=?", *p.ContestID).Scan(&cid) - if err == sql.ErrNoRows { - return ErrNotFound - } else if err != nil { - return err - } - } + if p.ContestID != nil { + var cid int64 + err := s.DB.QueryRowContext(ctx, "SELECT contest_id FROM contest WHERE contest_id=?", *p.ContestID).Scan(&cid) + if err == sql.ErrNoRows { + return ErrNotFound + } else if err != nil { + return err + } + } - res, err := s.DB.ExecContext(ctx, "INSERT INTO problem (contest_id, creator_id, problem_name, description) VALUES (?, ?, ?, ?)", p.ContestID, p.CreatorID, p.ProblemName, p.Description) - if err != nil { - return err - } - if id, ierr := res.LastInsertId(); ierr == nil { - p.ProblemID = id - } - return nil + res, err := s.DB.ExecContext(ctx, "INSERT INTO problem (contest_id, creator_id, problem_name, description) VALUES (?, ?, ?, ?)", p.ContestID, p.CreatorID, p.ProblemName, p.Description) + if err != nil { + return err + } + if id, ierr := res.LastInsertId(); ierr == nil { + p.ProblemID = id + } + return nil } From ee0a3f250e11af8ca3c6795966d1cacf0fd35335 Mon Sep 17 00:00:00 2001 From: Davidmunmen Date: Wed, 10 Dec 2025 17:51:45 -0600 Subject: [PATCH 3/5] Update ranking_users changes --- api/api_test.go | 2 +- api/submit.go | 6 ++--- judge/judge.go | 61 ++++++++++++++++++++++++++++++++++++++++++++----- schema.sql | 3 ++- store/sqlite.go | 53 ++++++++++++++++++++++++++++++++++++++++++ store/store.go | 11 +++++++++ 6 files changed, 125 insertions(+), 11 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index b6de023..e11d3c8 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -83,7 +83,7 @@ func TestCreateUser(t *testing.T) { clearUserTable() newUser := store.User{ Username: "testuser", - Passhash: "testpass", + Password: "testpass", Email: "test@example.com", } diff --git a/api/submit.go b/api/submit.go index da0950b..2c10c32 100644 --- a/api/submit.go +++ b/api/submit.go @@ -45,7 +45,7 @@ func (api *ApiContext) submitProgram(w http.ResponseWriter, r *http.Request) { fmt.Println(fileHeader.Size, fileHeader.Filename) m.Lock() - worker := <- *judge.WorkerQueueP + worker := <-*judge.WorkerQueueP m.Unlock() conn := worker.Ctx @@ -53,6 +53,7 @@ func (api *ApiContext) submitProgram(w http.ResponseWriter, r *http.Request) { filenameSep := strings.Split(fileHeader.Filename, ".") filetype := filenameSep[len(filenameSep)-1] + output := int(0) // testcases, err := api.Store.GetTestCases(r.Context(), problemID) testcases := []judge.SubmissionTestCase{ @@ -61,7 +62,7 @@ func (api *ApiContext) submitProgram(w http.ResponseWriter, r *http.Request) { judge.SubmissionTestCase{Input: "13\n", Output: "6227020800\n"}, } timelimit := 1 - createResponse, err := judge.CreateSandbox(conn, filetype, string(buf)[:n], testcases, timelimit) + createResponse, err := judge.CreateSandbox(conn, filetype, string(buf)[:n], testcases, timelimit, output) if err != nil { *judge.WorkerQueueP <- worker s := fmt.Sprintf("Could not create sandbox: %v", err) @@ -101,7 +102,6 @@ func (api *ApiContext) submitProgram(w http.ResponseWriter, r *http.Request) { // Metelo de nuevo *judge.WorkerQueueP <- worker - w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(resMap) diff --git a/judge/judge.go b/judge/judge.go index c80e950..a5dfc15 100644 --- a/judge/judge.go +++ b/judge/judge.go @@ -19,13 +19,14 @@ import ( "github.com/containers/podman/v5/pkg/domain/entities/types" "github.com/containers/podman/v5/pkg/specgen" dockerContainer "github.com/docker/docker/api/types/container" + "lidsol.org/papeador/store" _ "modernc.org/sqlite" ) var podmanConns []*Worker var workerQueue = make(chan *Worker, 10) -var WorkerQueueP = &workerQueue; +var WorkerQueueP = &workerQueue var m sync.Mutex @@ -36,8 +37,8 @@ func ConnectToPodman(connURI string) (context.Context, error) { } worker := &Worker{ - Uri: connURI, - Ctx: conn, + Uri: connURI, + Ctx: conn, Available: true, } podmanConns = append(podmanConns, worker) @@ -53,7 +54,6 @@ func CreateFiles(filetype, programStr string, testcases []SubmissionTestCase, ti m.Lock() defer m.Unlock() - err := os.MkdirAll("/vol/podman/inputs", 0755) if err != nil { return err @@ -96,7 +96,6 @@ func CreateFiles(filetype, programStr string, testcases []SubmissionTestCase, ti testInputPath := fmt.Sprintf("./inputs/%02d.txt", k) expectedOutputPath := fmt.Sprintf("./expected-outputs/%02d.txt", k) - err = writeStringToFile(testInputPath, testcase.Input) if err != nil { return err @@ -111,7 +110,7 @@ func CreateFiles(filetype, programStr string, testcases []SubmissionTestCase, ti return nil } -func CreateSandbox(conn context.Context, filetype, programStr string, testcases []SubmissionTestCase, timeLimit int) (types.ContainerCreateResponse, error) { +func CreateSandbox(conn context.Context, filetype, programStr string, testcases []SubmissionTestCase, timeLimit int, output int) (types.ContainerCreateResponse, error) { err := CreateFiles(filetype, programStr, testcases, timeLimit) @@ -176,3 +175,53 @@ func StartSandbox(conn context.Context, createResponse types.ContainerCreateResp return nil } + +func CalculateAndUpdateRanking( + ctx context.Context, + s store.Store, + userID int, + status string, + executionTime float64) ([]store.UserScore, error) { + + log.Printf("Iniciando cálculo de puntaje para el usuario: %d, status: %s", userID, status) + //calculate user score + input := store.ScoringInput{Status: status, ExecutionTime: executionTime} + score := calculateSingleScore(input) + + log.Printf("Puntaje calculado: %d", score) + + //funcion para cambiar el puntaje del usuario + if err := s.UserScore(ctx, userID, score); err != nil { + log.Printf("Error al actualizar puntaje del usuario %d: %v", userID, err) + return nil, fmt.Errorf("error al actualizar puntaje en store: %w", err) + } + + ranking, err := s.GetGlobalRanking(ctx) + if err != nil { + log.Printf("Error al obtener ranking: %v", err) + return nil, fmt.Errorf("error al obtener ranking del store: %w", err) + } + + PrintRanking(ranking) + return ranking, nil +} + +// --------------------------------------------------------- +// funcion adicional para imprimir el ranking +func PrintRanking(ranking []store.UserScore) { + fmt.Println("\n==============================================") + fmt.Println(" RANKING DE USUARIOS ") + fmt.Println("----------------------------------------------") + fmt.Printf("%-5s | %-15s | %s\n", "RANK", "USER", "POINTS") + fmt.Println("----------------------------------------------") + + for _, user := range ranking { + fmt.Printf("%-5d | %-15d | %d\n", user.Rank, user.UserID, user.Score) + } + fmt.Println("==============================================") +} + +// funcion provicional para calcular el puntaje de un solo usuario +func calculateSingleScore(sub store.ScoringInput) int { + return 0 +} diff --git a/schema.sql b/schema.sql index dc89191..a471e6b 100644 --- a/schema.sql +++ b/schema.sql @@ -5,7 +5,8 @@ create table if not exists user ( username text not null unique, passhash text not null, passsalt text not null, - email text not null unique + email text not null unique, + total_score integer not null default 0 ); create table if not exists status ( diff --git a/store/sqlite.go b/store/sqlite.go index f909433..09f200e 100644 --- a/store/sqlite.go +++ b/store/sqlite.go @@ -300,3 +300,56 @@ func (s *SQLiteStore) Login(ctx context.Context, u *User) error { return nil } +func (s *SQLiteStore) UserScore(ctx context.Context, userID int, score int) error { + _, err := s.DB.ExecContext( + ctx, + "UPDATE user SET total_score = total_score + ? WHERE user_id = ?", + score, + userID, + ) + if err != nil { + return err + } + + log.Printf("UserID %d: +%d puntos agregados", userID, score) + return nil +} + +func (s *SQLiteStore) GetGlobalRanking(ctx context.Context) ([]UserScore, error) { + var ranking []UserScore + + query := ` + SELECT user_id, total_score + FROM user + ORDER BY total_score DESC, user_id ASC; + ` + + rows, err := s.DB.QueryContext(ctx, query) + if err != nil { + return nil, err + } + defer rows.Close() + + rankCounter := 1 + for rows.Next() { + var userID int + var score int + + if err := rows.Scan(&userID, &score); err != nil { + return nil, err + } + + ranking = append(ranking, UserScore{ + Rank: rankCounter, + UserID: userID, + Score: score, + }) + rankCounter++ + } + + if rows.Err() != nil { + return nil, err + } + + return ranking, nil +} diff --git a/store/store.go b/store/store.go index c7ea8bb..dd0f035 100644 --- a/store/store.go +++ b/store/store.go @@ -48,6 +48,15 @@ type TestCase struct { ExpectedOut []byte `json:"expected_out"` GivenInput []byte `json:"given_input"` } +type UserScore struct { + Rank int `json:"rank"` + UserID int `json:"user_id"` + Score int `json:"score"` +} +type ScoringInput struct { + Status string + ExecutionTime float64 +} // Store defines persistence operations used by the HTTP layer. type Store interface { @@ -64,4 +73,6 @@ type Store interface { GetContestProblems(ctx context.Context, id int) ([]Problem, error) GetProblemByIDs(ctx context.Context, contestID, problemID int) (*Problem, error) GetTestCases(ctx context.Context, problemID int) ([]TestCase, error) + UserScore(ctx context.Context, userID int, score int) error + GetGlobalRanking(ctx context.Context) ([]UserScore, error) } From 08922cf46a0da60b216f58353a2a697602e0c811 Mon Sep 17 00:00:00 2001 From: Davidmunmen Date: Tue, 16 Dec 2025 18:21:24 -0600 Subject: [PATCH 4/5] change query --- judge/judge.go | 2 +- store/sqlite.go | 21 +++++++++++++++++---- store/store.go | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/judge/judge.go b/judge/judge.go index a5dfc15..6473de4 100644 --- a/judge/judge.go +++ b/judge/judge.go @@ -196,7 +196,7 @@ func CalculateAndUpdateRanking( return nil, fmt.Errorf("error al actualizar puntaje en store: %w", err) } - ranking, err := s.GetGlobalRanking(ctx) + ranking, err := s.GetRanking(ctx) if err != nil { log.Printf("Error al obtener ranking: %v", err) return nil, fmt.Errorf("error al obtener ranking del store: %w", err) diff --git a/store/sqlite.go b/store/sqlite.go index 09f200e..322b161 100644 --- a/store/sqlite.go +++ b/store/sqlite.go @@ -315,13 +315,26 @@ func (s *SQLiteStore) UserScore(ctx context.Context, userID int, score int) erro return nil } -func (s *SQLiteStore) GetGlobalRanking(ctx context.Context) ([]UserScore, error) { +func (s *SQLiteStore) GetRanking(ctx context.Context) ([]UserScore, error) { var ranking []UserScore query := ` - SELECT user_id, total_score - FROM user - ORDER BY total_score DESC, user_id ASC; + SELECT + subm.user_id, + USR.username, + SUM(subm.score) AS total_score + FROM + submission subm + JOIN + contest_has_problem C_h_p ON subm.problem_id = C_h_p.problem_id + JOIN + user USR ON subm.user_id = USR.user_id + WHERE + C_h_p.contest_id = ? + GROUP BY + subm.user_id, USR.username + ORDER BY + total_score DESC, subm.user_id ASC; ` rows, err := s.DB.QueryContext(ctx, query) diff --git a/store/store.go b/store/store.go index dd0f035..95d1240 100644 --- a/store/store.go +++ b/store/store.go @@ -74,5 +74,5 @@ type Store interface { GetProblemByIDs(ctx context.Context, contestID, problemID int) (*Problem, error) GetTestCases(ctx context.Context, problemID int) ([]TestCase, error) UserScore(ctx context.Context, userID int, score int) error - GetGlobalRanking(ctx context.Context) ([]UserScore, error) + GetRanking(ctx context.Context) ([]UserScore, error) } From bcd339402478fa5de756321f0e602ddb91485637 Mon Sep 17 00:00:00 2001 From: Davidmunmen Date: Wed, 17 Dec 2025 14:14:53 -0600 Subject: [PATCH 5/5] additional code cleaning --- judge/judge.go | 23 ++++------------------- store/sqlite.go | 14 -------------- store/store.go | 2 +- 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/judge/judge.go b/judge/judge.go index 6473de4..3864c70 100644 --- a/judge/judge.go +++ b/judge/judge.go @@ -19,7 +19,7 @@ import ( "github.com/containers/podman/v5/pkg/domain/entities/types" "github.com/containers/podman/v5/pkg/specgen" dockerContainer "github.com/docker/docker/api/types/container" - "lidsol.org/papeador/store" + _ "modernc.org/sqlite" ) @@ -176,6 +176,8 @@ func StartSandbox(conn context.Context, createResponse types.ContainerCreateResp return nil } +/* +///Open function to calculate and update ranking after a submission is judged (suggestion) func CalculateAndUpdateRanking( ctx context.Context, s store.Store, @@ -205,23 +207,6 @@ func CalculateAndUpdateRanking( PrintRanking(ranking) return ranking, nil } - -// --------------------------------------------------------- -// funcion adicional para imprimir el ranking -func PrintRanking(ranking []store.UserScore) { - fmt.Println("\n==============================================") - fmt.Println(" RANKING DE USUARIOS ") - fmt.Println("----------------------------------------------") - fmt.Printf("%-5s | %-15s | %s\n", "RANK", "USER", "POINTS") - fmt.Println("----------------------------------------------") - - for _, user := range ranking { - fmt.Printf("%-5d | %-15d | %d\n", user.Rank, user.UserID, user.Score) - } - fmt.Println("==============================================") -} - -// funcion provicional para calcular el puntaje de un solo usuario func calculateSingleScore(sub store.ScoringInput) int { return 0 -} +}*/ diff --git a/store/sqlite.go b/store/sqlite.go index 322b161..98730c2 100644 --- a/store/sqlite.go +++ b/store/sqlite.go @@ -300,20 +300,6 @@ func (s *SQLiteStore) Login(ctx context.Context, u *User) error { return nil } -func (s *SQLiteStore) UserScore(ctx context.Context, userID int, score int) error { - _, err := s.DB.ExecContext( - ctx, - "UPDATE user SET total_score = total_score + ? WHERE user_id = ?", - score, - userID, - ) - if err != nil { - return err - } - - log.Printf("UserID %d: +%d puntos agregados", userID, score) - return nil -} func (s *SQLiteStore) GetRanking(ctx context.Context) ([]UserScore, error) { var ranking []UserScore diff --git a/store/store.go b/store/store.go index 95d1240..54732ec 100644 --- a/store/store.go +++ b/store/store.go @@ -73,6 +73,6 @@ type Store interface { GetContestProblems(ctx context.Context, id int) ([]Problem, error) GetProblemByIDs(ctx context.Context, contestID, problemID int) (*Problem, error) GetTestCases(ctx context.Context, problemID int) ([]TestCase, error) - UserScore(ctx context.Context, userID int, score int) error + //UserScore(ctx context.Context, userID int, score int) error GetRanking(ctx context.Context) ([]UserScore, error) }