From b70bbf13dd681e2cea329152311da12865bd565c Mon Sep 17 00:00:00 2001 From: CamPlume1 Date: Thu, 14 Nov 2024 10:29:25 -0500 Subject: [PATCH 01/14] rebase on main --- backend/internal/github/github.go | 2 + .../github/sharedclient/sharedclient.go | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/backend/internal/github/github.go b/backend/internal/github/github.go index f0281444..33610567 100644 --- a/backend/internal/github/github.go +++ b/backend/internal/github/github.go @@ -48,6 +48,8 @@ type GitHubUserClient interface { // All methods in the OAUTH client GitHubCallback(code string, clientCfg config.GitHubUserClient) (string, error) ForkRepository(ctx context.Context, org, owner, repo, destName string) error + // Create Branch protections for a given repo in an org + CreatePushRuleset(ctx context.Context, orgName, repoName string) error } type GitHubBaseClient interface { //All methods in the SHARED client diff --git a/backend/internal/github/sharedclient/sharedclient.go b/backend/internal/github/sharedclient/sharedclient.go index 85ba8566..67f90e90 100644 --- a/backend/internal/github/sharedclient/sharedclient.go +++ b/backend/internal/github/sharedclient/sharedclient.go @@ -154,3 +154,41 @@ func (api *CommonAPI) GetUser(ctx context.Context, userName string) (*github.Use user, _, err := api.Client.Users.Get(ctx, userName) return user, err } + +//Given a repo name and org name, create a push ruleset to protect the .github directory +func (api *CommonAPI) CreatePushRuleset(ctx context.Context, orgName, repoName string) error { + endpoint := fmt.Sprintf("/repos/%s/%s/rulesets", orgName, repoName) + + body := map[string]interface{}{ + "name": "Restrict .github Directory Edits: Preserves Submission Deadline", + "target": "branch", + "enforcement": "active", + "rules": []map[string]interface{}{ + { + "type": "file_path", + "parameters": map[string]interface{}{ + "operator": "starts_with", + "pattern": ".github/", + }, + "actions": map[string]interface{}{ + "block": map[string]interface{}{ + "enabled": true, + "reason": "Modification of the .github directory is restricted. Please contact course staff for an extension", + }, + }, + }, + }, + } + req, err := api.Client.NewRequest("POST", endpoint, body) + if err != nil { + fmt.Printf("Request Construction failed") + } + + _, err = api.Client.Do(ctx, req, nil) + if err != nil { + fmt.Printf("request execution failed") + } + + return nil + +} \ No newline at end of file From 628f4fe351d2a9def0927fa73ddc46ca53065028 Mon Sep 17 00:00:00 2001 From: CamPlume1 Date: Tue, 3 Dec 2024 10:35:30 -0500 Subject: [PATCH 02/14] working ruleset pushes --- .gitignore | 4 +- backend/internal/github/actions.go | 27 +++++ backend/internal/github/github.go | 3 + .../github/sharedclient/sharedclient.go | 104 +++++++++++++----- .../classrooms/assignments/assignments.go | 57 +++++----- .../classrooms/assignments/service.go | 6 +- .../internal/handlers/classrooms/routes.go | 2 +- backend/internal/handlers/hello/routes.go | 11 +- .../handlers/organizations/organization.go | 3 +- .../handlers/organizations/service.go | 3 + backend/internal/models/repository_edit.go | 11 ++ 11 files changed, 170 insertions(+), 61 deletions(-) create mode 100644 backend/internal/github/actions.go create mode 100644 backend/internal/models/repository_edit.go diff --git a/.gitignore b/.gitignore index 0a62965f..991411e8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ go.work.sum # env file .env - +*.env_other* # Logs logs *.log @@ -57,4 +57,4 @@ terraform/.terraform/ *.tfstate *.tfstate.backup */terraform.tfstate* -*/terraform.tfvars \ No newline at end of file +*/terraform.tfvars diff --git a/backend/internal/github/actions.go b/backend/internal/github/actions.go new file mode 100644 index 00000000..a631786b --- /dev/null +++ b/backend/internal/github/actions.go @@ -0,0 +1,27 @@ +package github + +import "fmt" + +func dateCutScript(date string) string { + var scriptString = ` +from datetime import datetime +import sys + +def check_date(): + # Define the target date (November 27, 2024 midnight) + target_date = datetime(%s) + + # Get current date and time + current_date = datetime.now() + + # Compare dates and exit with appropriate code + if current_date > target_date: + sys.exit(1) + else: + sys.exit(0) + +if __name__ == "__main__": + check_date()` + + return fmt.Sprintf(scriptString, date) +} \ No newline at end of file diff --git a/backend/internal/github/github.go b/backend/internal/github/github.go index 33610567..31f32c7c 100644 --- a/backend/internal/github/github.go +++ b/backend/internal/github/github.go @@ -30,6 +30,8 @@ type GitHubAppClient interface { // All methods in the APP client // Add a repository permission to a user AssignPermissionToUser(ctx context.Context, ownerName string, repoName string, userName string, permission string) error + + CreateDeadlineEnforcement(ctx context.Context, orgName, repoName string) error } type GitHubUserClient interface { // All methods in the OAUTH client @@ -48,6 +50,7 @@ type GitHubUserClient interface { // All methods in the OAUTH client GitHubCallback(code string, clientCfg config.GitHubUserClient) (string, error) ForkRepository(ctx context.Context, org, owner, repo, destName string) error + // Create Branch protections for a given repo in an org CreatePushRuleset(ctx context.Context, orgName, repoName string) error } diff --git a/backend/internal/github/sharedclient/sharedclient.go b/backend/internal/github/sharedclient/sharedclient.go index 67f90e90..4c3fb0c8 100644 --- a/backend/internal/github/sharedclient/sharedclient.go +++ b/backend/internal/github/sharedclient/sharedclient.go @@ -2,6 +2,7 @@ package sharedclient import ( "context" + "encoding/base64" "fmt" "github.com/CamPlume1/khoury-classroom/internal/models" @@ -155,40 +156,93 @@ func (api *CommonAPI) GetUser(ctx context.Context, userName string) (*github.Use return user, err } + + +func (api *CommonAPI) createRuleSet(ctx context.Context, ruleset interface{}, orgName, repoName string) error { + fmt.Printf("Ruleset:%v\n\n", ruleset) + endpoint := fmt.Sprintf("/repos/%s/%s/rulesets", orgName, repoName) + req, err := api.Client.NewRequest("POST", endpoint, ruleset) + if err != nil { + fmt.Printf("Request Construction failed") + return err + } + + resp, err := api.Client.Do(ctx, req, nil) + if err != nil { + fmt.Printf("request execution failed\n\n") + fmt.Printf("Request:%v\n\n", req) + fmt.Printf("Response: %v\n\n", resp) + fmt.Printf("Error: %v\n\n", err) + return err + } + + return nil + + +} + //Given a repo name and org name, create a push ruleset to protect the .github directory func (api *CommonAPI) CreatePushRuleset(ctx context.Context, orgName, repoName string) error { - endpoint := fmt.Sprintf("/repos/%s/%s/rulesets", orgName, repoName) body := map[string]interface{}{ - "name": "Restrict .github Directory Edits: Preserves Submission Deadline", - "target": "branch", - "enforcement": "active", - "rules": []map[string]interface{}{ - { - "type": "file_path", - "parameters": map[string]interface{}{ - "operator": "starts_with", - "pattern": ".github/", - }, - "actions": map[string]interface{}{ - "block": map[string]interface{}{ - "enabled": true, - "reason": "Modification of the .github directory is restricted. Please contact course staff for an extension", - }, - }, - }, - }, - } - req, err := api.Client.NewRequest("POST", endpoint, body) + "name": "Restrict .github Directory Edits: Preserves Submission Deadline", + "target": "push", + "enforcement": "active", + "rules": []map[string]interface{}{ + { + "type": "file_path_restriction", + "parameters": map[string]interface{}{ + "restricted_file_paths": []string{".github/**/*"}, + + }, + }, + }, + } + return api.createRuleSet(ctx, body, orgName, repoName) +} + + + + + + +func (api *CommonAPI) CreateDeadlineEnforcement(ctx context.Context, orgName, repoName string) error { + + addition := models.RepositoryAddition{ + FilePath: ".github/workflows/deadline.yml", + RepoName: repoName, + OwnerName: orgName, + DestinationBranch: "main", + Content: "example", + CommitMessage: "Deadline enforcement GH action files", + } + return api.EditRepository(ctx, &addition) + +} + +func (api *CommonAPI) EditRepository(ctx context.Context, addition *models.RepositoryAddition) error { + fmt.Println("Reached function correctly") + endpoint := fmt.Sprintf("/repos/%s/%s/contents/%s", addition.OwnerName, addition.RepoName, addition.FilePath) + encodedContent := base64.StdEncoding.EncodeToString([]byte(addition.Content)) + + body := map[string]interface{}{ + "message": addition.CommitMessage, + "content": encodedContent, + "branch": addition.DestinationBranch, + } + + req, err := api.Client.NewRequest("PUT", endpoint, body) + fmt.Println("Body: ", req) if err != nil { - fmt.Printf("Request Construction failed") + fmt.Printf("Request Construction failed: %s", addition.CommitMessage) } - _, err = api.Client.Do(ctx, req, nil) + resp, err := api.Client.Do(ctx, req, nil) if err != nil { - fmt.Printf("request execution failed") + fmt.Printf("request execution failed: %s", addition.CommitMessage) + fmt.Println(err.Error()) } + fmt.Println(resp) return nil - } \ No newline at end of file diff --git a/backend/internal/handlers/classrooms/assignments/assignments.go b/backend/internal/handlers/classrooms/assignments/assignments.go index c1a77c27..481fa2b6 100644 --- a/backend/internal/handlers/classrooms/assignments/assignments.go +++ b/backend/internal/handlers/classrooms/assignments/assignments.go @@ -72,12 +72,12 @@ func (s *AssignmentService) createAssignment() fiber.Handler { func (s *AssignmentService) acceptAssignment() fiber.Handler { return func(c *fiber.Ctx) error { - // Check + parse FE request - var assignment models.AssignmentAcceptRequest - err := c.BodyParser(&assignment) - if err != nil { - return errs.InvalidRequestBody(models.AssignmentOutline{}) - } + // // Check + parse FE request + // var assignment models.AssignmentAcceptRequest + // err := c.BodyParser(&assignment) + // if err != nil { + // return errs.InvalidRequestBody(models.AssignmentOutline{}) + // } //Retrieve user client client, err := middleware.GetClient(c, s.store, s.userCfg) @@ -85,27 +85,30 @@ func (s *AssignmentService) acceptAssignment() fiber.Handler { return errs.AuthenticationError() } - // Retrieve current session - user, err := client.GetCurrentUser(c.Context()) - if err != nil { - return errs.GithubAPIError(err) - } - - //Insert into DB - forkName := generateForkName(assignment.SourceRepoName, user.Login) - studentwork := createMockStudentWork(forkName, assignment.AssignmentName, int(assignment.AssignmentID)) - err = s.store.CreateStudentWork(c.Context(), &studentwork, user.ID) - if err != nil { - return errs.InternalServerError() - } - - // Generate Fork via GH User - err = client.ForkRepository(c.Context(), assignment.OrgName, assignment.OrgName, assignment.SourceRepoName, forkName) - if err != nil { - return errs.InternalServerError() - } - - c.Status(http.StatusOK) + client.CreatePushRuleset(c.Context(), "NUSpecialProjects", "push_ruleset_poc") + //client.CreatePushRuleset(c.Context(), "CS-3500-OOD", "ruleset_poc_2") + + // // Retrieve current session + // user, err := client.GetCurrentUser(c.Context()) + // if err != nil { + // return errs.GithubAPIError(err) + // } + + // //Insert into DB + // forkName := generateForkName(assignment.SourceRepoName, user.Login) + // studentwork := createMockStudentWork(forkName, assignment.AssignmentName, int(assignment.AssignmentID)) + // err = s.store.CreateStudentWork(c.Context(), &studentwork, user.ID) + // if err != nil { + // return errs.InternalServerError() + // } + + // // Generate Fork via GH User + // err = client.ForkRepository(c.Context(), assignment.OrgName, assignment.OrgName, assignment.SourceRepoName, forkName) + // if err != nil { + // return errs.InternalServerError() + // } + + // c.Status(http.StatusOK) return nil } } diff --git a/backend/internal/handlers/classrooms/assignments/service.go b/backend/internal/handlers/classrooms/assignments/service.go index aeaefe6b..1122f777 100644 --- a/backend/internal/handlers/classrooms/assignments/service.go +++ b/backend/internal/handlers/classrooms/assignments/service.go @@ -2,15 +2,17 @@ package assignments import ( "github.com/CamPlume1/khoury-classroom/internal/config" + "github.com/CamPlume1/khoury-classroom/internal/github" "github.com/CamPlume1/khoury-classroom/internal/storage" ) type AssignmentService struct { store storage.Storage userCfg *config.GitHubUserClient + appClient github.GitHubAppClient } -func NewAssignmentService(store storage.Storage, userCfg *config.GitHubUserClient) *AssignmentService { - return &AssignmentService{store: store, userCfg: userCfg} +func NewAssignmentService(store storage.Storage, userCfg *config.GitHubUserClient, appClient github.GitHubAppClient) *AssignmentService { + return &AssignmentService{store: store, userCfg: userCfg, appClient: appClient} } diff --git a/backend/internal/handlers/classrooms/routes.go b/backend/internal/handlers/classrooms/routes.go index 45944c9c..8c2e5492 100644 --- a/backend/internal/handlers/classrooms/routes.go +++ b/backend/internal/handlers/classrooms/routes.go @@ -10,7 +10,7 @@ import ( func Routes(app *fiber.App, params types.Params) { classroomService := newClassroomService(params.Store, ¶ms.UserCfg) - assignmentService := assignments.NewAssignmentService(params.Store, ¶ms.UserCfg) + assignmentService := assignments.NewAssignmentService(params.Store, ¶ms.UserCfg, params.GitHubApp) workService := works.NewWorkService(params.Store, params.GitHubApp) // Create the base router diff --git a/backend/internal/handlers/hello/routes.go b/backend/internal/handlers/hello/routes.go index 6a3e96c0..06e10be9 100644 --- a/backend/internal/handlers/hello/routes.go +++ b/backend/internal/handlers/hello/routes.go @@ -8,7 +8,12 @@ import ( "github.com/gofiber/fiber/v2" ) -// Create HelloGroup fiber route group +// Routes initializes the "hello" and "hello_protected" route groups in the Fiber application. +// It sets up middleware for protected routes and registers endpoints. +// +// Parameters: +// - app: The Fiber application instance to which the routes will be added. +// - params: A Params struct containing configuration such as the data store and JWT secret. func Routes(app *fiber.App, params types.Params) { service := newService(params.Store) @@ -18,10 +23,10 @@ func Routes(app *fiber.App, params types.Params) { // Register Middleware protected.Use(middleware.Protected(params.UserCfg.JWTSecret)) - //Unprotected Routes + // Unprotected Routes unprotected := app.Group("/hello") - //Endpoints + // Endpoints protected.Get("/world", service.HelloWorld) unprotected.Get("/world", service.HelloWorld) } diff --git a/backend/internal/handlers/organizations/organization.go b/backend/internal/handlers/organizations/organization.go index 5d106d3d..8febc340 100644 --- a/backend/internal/handlers/organizations/organization.go +++ b/backend/internal/handlers/organizations/organization.go @@ -16,6 +16,7 @@ func (service *OrganizationService) GetOrgsAndClassrooms() fiber.Handler { } } +//Get all organizations a user is in func (service *OrganizationService) GetUserOrgs() fiber.Handler { return func(c *fiber.Ctx) error { client, err := middleware.GetClient(c, service.store, service.userCfg) @@ -23,11 +24,11 @@ func (service *OrganizationService) GetUserOrgs() fiber.Handler { return errs.GithubClientError(err) } + // Retrieve orgs from user client orgs, err := client.GetUserOrgs(c.Context()) if err != nil { return errs.GithubAPIError(err) } - return c.Status(fiber.StatusOK).JSON(fiber.Map{"orgs": orgs}) } } diff --git a/backend/internal/handlers/organizations/service.go b/backend/internal/handlers/organizations/service.go index b9f4f670..abf3cdfe 100644 --- a/backend/internal/handlers/organizations/service.go +++ b/backend/internal/handlers/organizations/service.go @@ -6,12 +6,15 @@ import ( "github.com/CamPlume1/khoury-classroom/internal/storage" ) + +//Service Declaration type OrganizationService struct { store storage.Storage githubappclient github.GitHubAppClient userCfg *config.GitHubUserClient } +//Service constructor func NewOrganizationService( store storage.Storage, githubappclient github.GitHubAppClient, diff --git a/backend/internal/models/repository_edit.go b/backend/internal/models/repository_edit.go new file mode 100644 index 00000000..1fd22cef --- /dev/null +++ b/backend/internal/models/repository_edit.go @@ -0,0 +1,11 @@ +package models + + +type RepositoryAddition struct { + FilePath string + RepoName string + OwnerName string + DestinationBranch string + Content string + CommitMessage string +} \ No newline at end of file From c8b845c7ee34f1945fee58b85acde9d8bf035afc Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:51:27 -0500 Subject: [PATCH 03/14] working happy path end-to-end assignment creation --- backend/internal/github/actions.go | 27 ------ backend/internal/github/github.go | 16 +++- .../github/sharedclient/sharedclient.go | 87 +++++++++++++++++-- backend/internal/handlers/auth/auth.go | 27 +++--- .../classrooms/assignments/assignments.go | 28 ++++++ .../handlers/classrooms/classrooms.go | 2 + .../handlers/organizations/organization.go | 3 + .../internal/handlers/webhooks/webhooks.go | 45 +++++++--- backend/internal/utils/actions.go | 48 ++++++++++ 9 files changed, 219 insertions(+), 64 deletions(-) delete mode 100644 backend/internal/github/actions.go create mode 100644 backend/internal/utils/actions.go diff --git a/backend/internal/github/actions.go b/backend/internal/github/actions.go deleted file mode 100644 index a631786b..00000000 --- a/backend/internal/github/actions.go +++ /dev/null @@ -1,27 +0,0 @@ -package github - -import "fmt" - -func dateCutScript(date string) string { - var scriptString = ` -from datetime import datetime -import sys - -def check_date(): - # Define the target date (November 27, 2024 midnight) - target_date = datetime(%s) - - # Get current date and time - current_date = datetime.now() - - # Compare dates and exit with appropriate code - if current_date > target_date: - sys.exit(1) - else: - sys.exit(0) - -if __name__ == "__main__": - check_date()` - - return fmt.Sprintf(scriptString, date) -} \ No newline at end of file diff --git a/backend/internal/github/github.go b/backend/internal/github/github.go index 8d0eba3a..4031522a 100644 --- a/backend/internal/github/github.go +++ b/backend/internal/github/github.go @@ -2,7 +2,7 @@ package github import ( "context" - + "time" "github.com/CamPlume1/khoury-classroom/internal/models" "github.com/google/go-github/github" ) @@ -30,7 +30,8 @@ type GitHubAppClient interface { // All methods in the APP client // Add a repository permission to a user AssignPermissionToUser(ctx context.Context, ownerName string, repoName string, userName string, permission string) error - CreateDeadlineEnforcement(ctx context.Context, orgName, repoName string) error + CreateDeadlineEnforcement(ctx context.Context, deadline *time.Time, orgName, repoName string) error + // Create instance of template repository CreateRepoFromTemplate(ctx context.Context, orgName, templateRepoName, newRepoName string) (*models.AssignmentBaseRepo, error) } @@ -56,7 +57,7 @@ type GitHubUserClient interface { // All methods in the OAUTH client ForkRepository(ctx context.Context, org, owner, repo, destName string) error // Create Branch protections for a given repo in an org - CreatePushRuleset(ctx context.Context, orgName, repoName string) error + //CreatePushRuleset(ctx context.Context, orgName, repoName string) error } type GitHubBaseClient interface { //All methods in the SHARED client @@ -105,4 +106,13 @@ type GitHubBaseClient interface { //All methods in the SHARED client // Remove repository from team RemoveRepoFromTeam(ctx context.Context, org, teamSlug, owner, repo string) error + + //Create push ruleset to protect .github folders + CreatePushRuleset(ctx context.Context, orgName, repoName string) error + + //Create rulesets to protect corresponding branches + CreateBranchRuleset(ctx context.Context, orgName, repoName string) error + + //Creates PR enforcements + CreatePREnforcement(ctx context.Context, orgName, repoName string) error } diff --git a/backend/internal/github/sharedclient/sharedclient.go b/backend/internal/github/sharedclient/sharedclient.go index 495bc263..5088bc85 100644 --- a/backend/internal/github/sharedclient/sharedclient.go +++ b/backend/internal/github/sharedclient/sharedclient.go @@ -3,10 +3,14 @@ package sharedclient import ( "context" "encoding/base64" + + //"encoding/json" "fmt" + "time" "github.com/CamPlume1/khoury-classroom/internal/errs" "github.com/CamPlume1/khoury-classroom/internal/models" + "github.com/CamPlume1/khoury-classroom/internal/utils" "github.com/google/go-github/github" ) @@ -67,6 +71,7 @@ func (api *CommonAPI) getBranchHead(ctx context.Context, owner, repo, branchName } func (api *CommonAPI) CreateBranch(ctx context.Context, owner, repo, baseBranch, newBranchName string) (*github.Reference, error) { + fmt.Println("Creating branch!!!") endpoint := fmt.Sprintf("/repos/%s/%s/git/refs", owner, repo) // Get the SHA of the base branch @@ -187,7 +192,6 @@ func (api *CommonAPI) GetUser(ctx context.Context, userName string) (*github.Use func (api *CommonAPI) createRuleSet(ctx context.Context, ruleset interface{}, orgName, repoName string) error { - fmt.Printf("Ruleset:%v\n\n", ruleset) endpoint := fmt.Sprintf("/repos/%s/%s/rulesets", orgName, repoName) req, err := api.Client.NewRequest("POST", endpoint, ruleset) if err != nil { @@ -211,17 +215,17 @@ func (api *CommonAPI) createRuleSet(ctx context.Context, ruleset interface{}, or //Given a repo name and org name, create a push ruleset to protect the .github directory func (api *CommonAPI) CreatePushRuleset(ctx context.Context, orgName, repoName string) error { + //fmt.Println("Creating Push Ruleset") body := map[string]interface{}{ "name": "Restrict .github Directory Edits: Preserves Submission Deadline", "target": "push", "enforcement": "active", - "rules": []map[string]interface{}{ - { + "rules": []interface{}{ + map[string]interface{}{ "type": "file_path_restriction", "parameters": map[string]interface{}{ "restricted_file_paths": []string{".github/**/*"}, - }, }, }, @@ -231,17 +235,83 @@ func (api *CommonAPI) CreatePushRuleset(ctx context.Context, orgName, repoName s +func (api *CommonAPI) CreateBranchRuleset(ctx context.Context, orgName, repoName string) error { + //fmt.Println("Creating Branch Ruleset") + body := map[string]interface{}{ + "name": "protect feedback from edits", + "target": "branch", + "enforcement": "active", + "conditions": map[string]interface{}{ + "ref_name": map[string]interface{}{ + "exclude": []interface{}{}, + "include": []interface{}{"refs/heads/feedback", "~DEFAULT_BRANCH"}, + }, + }, + "rules": []interface{}{ + map[string]interface{}{ + "type": "non_fast_forward", + }, + map[string]interface{}{ + "type": "pull_request", + "parameters" : map[string]interface{}{ + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "automatic_copilot_code_review_enabled": false, + }, + }, + map[string]interface{}{ + "type": "required_status_checks", + "parameters": map[string]interface{}{ + "strict_required_status_checks_policy": false, + "do_not_enforce_on_create": false, + "required_status_checks": []map[string]string{ + map[string]string{ + "context": "check-date", + }, + map[string]string{ + "context": "check-target", + }, + + }, + + }, + }, + }, + } + + return api.createRuleSet(ctx, body, orgName, repoName) +} -func (api *CommonAPI) CreateDeadlineEnforcement(ctx context.Context, orgName, repoName string) error { + + +func (api *CommonAPI) CreateDeadlineEnforcement(ctx context.Context, deadline *time.Time, orgName, repoName string) error { addition := models.RepositoryAddition{ FilePath: ".github/workflows/deadline.yml", RepoName: repoName, OwnerName: orgName, DestinationBranch: "main", - Content: "example", + Content: utils.ActionWithDeadlineStub(), + CommitMessage: "Deadline enforcement GH action files", + } + return api.EditRepository(ctx, &addition) + +} + + +func (api *CommonAPI) CreatePREnforcement(ctx context.Context, orgName, repoName string) error { + + addition := models.RepositoryAddition{ + FilePath: ".github/workflows/branchProtections.yml", + RepoName: repoName, + OwnerName: orgName, + DestinationBranch: "main", + Content: utils.TargetBranchProtectionAction(), CommitMessage: "Deadline enforcement GH action files", } return api.EditRepository(ctx, &addition) @@ -249,7 +319,7 @@ func (api *CommonAPI) CreateDeadlineEnforcement(ctx context.Context, orgName, re } func (api *CommonAPI) EditRepository(ctx context.Context, addition *models.RepositoryAddition) error { - fmt.Println("Reached function correctly") + fmt.Println("Reached function correctly- Repository Edit\n\n") endpoint := fmt.Sprintf("/repos/%s/%s/contents/%s", addition.OwnerName, addition.RepoName, addition.FilePath) encodedContent := base64.StdEncoding.EncodeToString([]byte(addition.Content)) @@ -265,13 +335,12 @@ func (api *CommonAPI) EditRepository(ctx context.Context, addition *models.Repos fmt.Printf("Request Construction failed: %s", addition.CommitMessage) } - resp, err := api.Client.Do(ctx, req, nil) + _, err = api.Client.Do(ctx, req, nil) if err != nil { fmt.Printf("request execution failed: %s", addition.CommitMessage) fmt.Println(err.Error()) } - fmt.Println(resp) return nil } func (api *CommonAPI) InviteUserToOrganization(ctx context.Context, orgName string, userID int64) error { diff --git a/backend/internal/handlers/auth/auth.go b/backend/internal/handlers/auth/auth.go index cb4e0d3f..d4e92b93 100644 --- a/backend/internal/handlers/auth/auth.go +++ b/backend/internal/handlers/auth/auth.go @@ -1,7 +1,8 @@ package auth import ( - "fmt" + //"fmt" + "net/url" "strconv" "strings" "time" @@ -25,17 +26,19 @@ func (service *AuthService) Ping() fiber.Handler { } func (service *AuthService) GetCallbackURL() fiber.Handler { - return func(c *fiber.Ctx) error { - oAuthCfg := service.userCfg.OAuthConfig() - clientID := oAuthCfg.ClientID - redirectURI := oAuthCfg.RedirectURL - scope := strings.Join(service.userCfg.Scopes, ",") - allowSignup := "false" - authURL := fmt.Sprintf("https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&scope=%s&allow_signup=%s", - clientID, redirectURI, scope, allowSignup) - - return c.Status(fiber.StatusOK).JSON(fiber.Map{"url": authURL}) - } + return func(c *fiber.Ctx) error { + oAuthCfg := service.userCfg.OAuthConfig() + // Build URL with proper encoding + params := url.Values{ + "client_id": {oAuthCfg.ClientID}, + "redirect_uri": {oAuthCfg.RedirectURL}, + "scope": {strings.Join(oAuthCfg.Scopes, ",")}, + "allow_signup": {"false"}, + "prompt": {"consent"}, // Force consent screen + } + authURL := "https://github.com/login/oauth/authorize?" + params.Encode() + return c.Status(fiber.StatusOK).JSON(fiber.Map{"url": authURL}) + } } func (service *AuthService) Login() fiber.Handler { diff --git a/backend/internal/handlers/classrooms/assignments/assignments.go b/backend/internal/handlers/classrooms/assignments/assignments.go index 812180b0..7b0ce38a 100644 --- a/backend/internal/handlers/classrooms/assignments/assignments.go +++ b/backend/internal/handlers/classrooms/assignments/assignments.go @@ -2,6 +2,8 @@ package assignments import ( "errors" + "fmt" + //"fmt" "net/http" "strconv" "strings" @@ -54,7 +56,11 @@ func (s *AssignmentService) getAssignment() fiber.Handler { } func (s *AssignmentService) createAssignment() fiber.Handler { + //TODO: .github folder addition + //TODO: push rulesets + //TODO: Branch protections (maybe) return func(c *fiber.Ctx) error { + fmt.Println("Creating Assignment/n/n") // Parse request body var assignmentData models.AssignmentOutline error := c.BodyParser(&assignmentData) @@ -62,6 +68,7 @@ func (s *AssignmentService) createAssignment() fiber.Handler { return errs.InvalidRequestBody(assignmentData) } + // Error if assignment already exists existingAssignment, err := s.store.GetAssignmentByNameAndClassroomID(c.Context(), assignmentData.Name, assignmentData.ClassroomID) if err != nil && !errors.Is(err, pgx.ErrNoRows) { @@ -99,6 +106,19 @@ func (s *AssignmentService) createAssignment() fiber.Handler { return err } + // //Create deadline enforcement if one is given + // if assignmentData.MainDueDate != nil { + + // err = s.appClient.CreatePushRuleset(c.Context(), classroom.OrgName, baseRepoName) + // if err != nil { + // fmt.Println("Skill Issue #2") + // } + // err = s.appClient.CreateBranchRuleset(c.Context(), classroom.OrgName, baseRepoName) + // if err != nil { + // fmt.Println("Skill Issue #3") + // } + // } + return c.Status(http.StatusOK).JSON(fiber.Map{ "created_assignment": createdAssignment, }) @@ -148,6 +168,7 @@ func (s *AssignmentService) generateAssignmentToken() fiber.Handler { // Uses an assignment token to accept an assignment. func (s *AssignmentService) useAssignmentToken() fiber.Handler { + //@CamTODO-> Downgrade student access return func(c *fiber.Ctx) error { token := c.Params("token") if token == "" { @@ -225,6 +246,13 @@ func (s *AssignmentService) useAssignmentToken() fiber.Handler { initialDelay *= 2 } + //@CamTODO: Get rid of happy path here with repo get fail + err = client.CreateBranchRuleset(c.Context(), classroom.OrgName, forkName) + if err != nil { + fmt.Printf("Error creating branch ruleset\n\n") + fmt.Println(err.Error()) + } + // Remove student team's access to forked repo err = client.RemoveRepoFromTeam(c.Context(), classroom.OrgName, *classroom.StudentTeamName, classroom.OrgName, *studentWorkRepo.Name) if err != nil { diff --git a/backend/internal/handlers/classrooms/classrooms.go b/backend/internal/handlers/classrooms/classrooms.go index b676fd9a..b3e8da43 100644 --- a/backend/internal/handlers/classrooms/classrooms.go +++ b/backend/internal/handlers/classrooms/classrooms.go @@ -2,6 +2,7 @@ package classrooms import ( "context" + "fmt" "net/http" "strconv" "strings" @@ -390,6 +391,7 @@ func (s *ClassroomService) getCurrentClassroomUser() fiber.Handler { } if classroomUser.Status == models.UserStatusNotInOrg { + fmt.Println("Error correctly identified") return errs.InconsistentOrgMembershipError() } diff --git a/backend/internal/handlers/organizations/organization.go b/backend/internal/handlers/organizations/organization.go index 794ec3bb..92a04282 100644 --- a/backend/internal/handlers/organizations/organization.go +++ b/backend/internal/handlers/organizations/organization.go @@ -1,6 +1,7 @@ package organizations import ( + //"fmt" "strconv" "github.com/CamPlume1/khoury-classroom/internal/errs" @@ -45,12 +46,14 @@ func (service *OrganizationService) GetInstalledOrgs() fiber.Handler { // Get the list of organizations the user is part of userOrgs, err := userClient.GetUserOrgs(c.Context()) + //fmt.Println(userOrgs) if err != nil { return errs.GithubAPIError(err) } // Get the list of installations of the GitHub app appInstallations, err := appClient.ListInstallations(c.Context()) + //fmt.Println(appInstallations) if err != nil { return errs.GithubAPIError(err) } diff --git a/backend/internal/handlers/webhooks/webhooks.go b/backend/internal/handlers/webhooks/webhooks.go index 792d5ee2..03acf4df 100644 --- a/backend/internal/handlers/webhooks/webhooks.go +++ b/backend/internal/handlers/webhooks/webhooks.go @@ -1,8 +1,10 @@ package webhooks import ( - "encoding/json" + //"encoding/json" "errors" + "fmt" + "time" "github.com/CamPlume1/khoury-classroom/internal/errs" models "github.com/CamPlume1/khoury-classroom/internal/models" @@ -49,17 +51,12 @@ func (s *WebHookService) PRThread(c *fiber.Ctx) error { } func (s *WebHookService) PushEvent(c *fiber.Ctx) error { + fmt.Println("Push Event Triggered") + fmt.Println(c.AllParams()) // Extract the 'payload' form value - payload := c.FormValue("payload") - if payload == "" { - return errs.BadRequest(errors.New("missing payload")) - } - - // Unmarshal the JSON payload into the PushEvent struct - var pushEvent github.PushEvent - err := json.Unmarshal([]byte(payload), &pushEvent) - if err != nil { - return errs.BadRequest(errors.New("invalid JSON payload")) + pushEvent := github.PushEvent{} + if err := c.BodyParser(&pushEvent); err != nil { + return err } // Check if this is first commit in a repository @@ -84,10 +81,25 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P return errs.BadRequest(errors.New("invalid repository data")) } - // Create necessary repo branches + stubTime := time.Now() + + //TODO: Deadline commit here + err := s.appClient.CreateDeadlineEnforcement(c.Context(), &stubTime, *pushEvent.Repo.Organization, *pushEvent.Repo.Name) + if err != nil { + fmt.Println(err) + fmt.Println("DEADLINE ENFORCEMENT FAILED") + } + + err = s.appClient.CreatePREnforcement(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) + if err != nil { + fmt.Println(err) + fmt.Println("DEADLINE ENFORCEMENT FAILED") + } + + //Create necessary repo branches repoBranches := []string{"development", "feedback"} for _, branch := range repoBranches { - _, err := s.appClient.CreateBranch(c.Context(), + _, err = s.appClient.CreateBranch(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name, *pushEvent.Repo.MasterBranch, @@ -98,6 +110,13 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P } } + + err = s.appClient.CreatePushRuleset(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) + if err != nil { + fmt.Printf("Error creating push ruleset") + fmt.Println(err.Error()) + } + // Find the associated assignment and classroom assignmentOutline, err := s.store.GetAssignmentByBaseRepoID(c.Context(), *pushEvent.Repo.ID) if err != nil { diff --git a/backend/internal/utils/actions.go b/backend/internal/utils/actions.go new file mode 100644 index 00000000..3184b73e --- /dev/null +++ b/backend/internal/utils/actions.go @@ -0,0 +1,48 @@ +package utils + +import "fmt" + +func ActionWithDeadlineStub() string { + var scriptString = ` +from datetime import datetime +import sys + +def check_date(): + # Define the target date + target_date = datetime(2024, 12, 20, 0, 0, 0, tzinfo=timezone.utc) + + # Get current date and time + current_date = datetime.now() + + # Compare dates and exit with appropriate code + if current_date > target_date: + sys.exit(1) + else: + sys.exit(0) + +if __name__ == "__main__": + check_date()` + + return fmt.Sprintf(scriptString) +} + + +func TargetBranchProtectionAction() string { + var actionString = `name: Check PR Target Branch + +on: + pull_request: + types: [opened, reopened, edited, synchronize] + +jobs: + check-target: + runs-on: ubuntu-latest + steps: + - name: Check PR destination branch + run: | + if [[ "${{ github.event.pull_request.base.ref }}" == "grading" ]]; then + echo "Error: Pull requests targeting the 'grading' branch are not allowed" + exit 1 + fi` + return actionString +} \ No newline at end of file From 77d21980c5697b8a3a83d431800d04cd23bd7cf9 Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:22:25 -0500 Subject: [PATCH 04/14] adding new remote for some reason --- backend/internal/storage/postgres/works.go | 9 +++++++++ backend/internal/storage/storage.go | 1 + 2 files changed, 10 insertions(+) diff --git a/backend/internal/storage/postgres/works.go b/backend/internal/storage/postgres/works.go index e85c4079..4af2b4f9 100644 --- a/backend/internal/storage/postgres/works.go +++ b/backend/internal/storage/postgres/works.go @@ -137,6 +137,15 @@ WHERE student_work_id = $3 return formatted[0], nil } +func (db *DB) GetWorkFromRepoName(ctx context.Context, repoName string) (*models.StudentWork, error){ + var studentWork models.StudentWork + err := db.connPool.QueryRow(ctx, `SELECT * FROM student_works WHERE repo_name = $1`, repoName).Scan(&studentWork) + if err != nil { + return nil, err + } + return &studentWork, nil +} + func (db *DB) CreateStudentWork(ctx context.Context, assignmentOutlineID int32, gitHubUserID int64, repoName string, workState models.WorkState, dueDate *time.Time) (models.StudentWork, error) { var studentWork models.StudentWork diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index 41093a28..a95ea04d 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -30,6 +30,7 @@ type FeedbackComment interface { type Works interface { GetWorks(ctx context.Context, classroomID int, assignmentID int) ([]*models.StudentWorkWithContributors, error) GetWork(ctx context.Context, classroomID int, assignmentID int, studentWorkID int) (*models.PaginatedStudentWorkWithContributors, error) + GetWorkFromRepoName(ctx context.Context, repoName string) (models.StudentWork, error) CreateStudentWork(ctx context.Context, assignmentOutlineID int32, gitHubUserID int64, repoName string, workState models.WorkState, dueDate *time.Time) (models.StudentWork, error) } From 29f03e276acdfde63ea746d5ada41b5e2a311084 Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:00:41 -0500 Subject: [PATCH 05/14] deadline enforcement, broken app settings --- .../internal/handlers/webhooks/webhooks.go | 37 ++++++++++++------- .../storage/postgres/assignment_templates.go | 13 +++++++ backend/internal/storage/postgres/works.go | 16 ++++---- backend/internal/storage/storage.go | 2 +- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/backend/internal/handlers/webhooks/webhooks.go b/backend/internal/handlers/webhooks/webhooks.go index 03acf4df..eda077c9 100644 --- a/backend/internal/handlers/webhooks/webhooks.go +++ b/backend/internal/handlers/webhooks/webhooks.go @@ -4,7 +4,7 @@ import ( //"encoding/json" "errors" "fmt" - "time" + //"time" "github.com/CamPlume1/khoury-classroom/internal/errs" models "github.com/CamPlume1/khoury-classroom/internal/models" @@ -81,19 +81,21 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P return errs.BadRequest(errors.New("invalid repository data")) } - stubTime := time.Now() + // Retrieve assignment deadline from DB + deadline, err := s.store.GetAssignmentDueDateByRepoName(c.Context(), *pushEvent.Repo.Name) + if err == nil { + // There is a deadline + err = s.appClient.CreateDeadlineEnforcement(c.Context(), deadline, *pushEvent.Repo.Organization, *pushEvent.Repo.Name) + if err != nil { + fmt.Println(err) + fmt.Println("DEADLINE ENFORCEMENT FAILED") + } - //TODO: Deadline commit here - err := s.appClient.CreateDeadlineEnforcement(c.Context(), &stubTime, *pushEvent.Repo.Organization, *pushEvent.Repo.Name) - if err != nil { - fmt.Println(err) - fmt.Println("DEADLINE ENFORCEMENT FAILED") } err = s.appClient.CreatePREnforcement(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { - fmt.Println(err) - fmt.Println("DEADLINE ENFORCEMENT FAILED") + return err } //Create necessary repo branches @@ -113,25 +115,32 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P err = s.appClient.CreatePushRuleset(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { - fmt.Printf("Error creating push ruleset") - fmt.Println(err.Error()) + // TODO: Recovery. Will do in a new ticket. For now, log + fmt.Errorf(err.Error()) + return err } // Find the associated assignment and classroom assignmentOutline, err := s.store.GetAssignmentByBaseRepoID(c.Context(), *pushEvent.Repo.ID) if err != nil { - return errs.InternalServerError() + // TODO: Recovery. Will do in a new ticket. For now, log + fmt.Errorf(err.Error()) + return err } classroom, err := s.store.GetClassroomByID(c.Context(), assignmentOutline.ClassroomID) if err != nil { - return errs.InternalServerError() + // TODO: Recovery. Will do in a new ticket. For now, log + fmt.Errorf(err.Error()) + return err } // Give the student team read access to the repository err = s.appClient.UpdateTeamRepoPermissions(c.Context(), *pushEvent.Repo.Organization, *classroom.StudentTeamName, *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "pull") if err != nil { - return errs.InternalServerError() + // TODO: Recovery. Will do in a new ticket. For now, log + fmt.Errorf(err.Error()) + return err } return c.SendStatus(200) diff --git a/backend/internal/storage/postgres/assignment_templates.go b/backend/internal/storage/postgres/assignment_templates.go index d8a6994a..3272153d 100644 --- a/backend/internal/storage/postgres/assignment_templates.go +++ b/backend/internal/storage/postgres/assignment_templates.go @@ -2,6 +2,7 @@ package postgres import ( "context" + "time" "github.com/CamPlume1/khoury-classroom/internal/errs" "github.com/CamPlume1/khoury-classroom/internal/models" @@ -50,3 +51,15 @@ func (db *DB) GetAssignmentTemplateByID(ctx context.Context, templateID int64) ( return assignmentTemplate, nil } + +func (db *DB) GetAssignmentDueDateByRepoName(ctx context.Context, repoName string) (*time.Time, error){ + var dueDate time.Time + err := db.connPool.QueryRow(ctx, `SELECT ao.main_due_date + FROM assignment_outlines ao + JOIN assignment_templates at ON ao.template_id = at.template_repo_id + WHERE at.template_repo_name = 'your_template_repo_name';`, repoName).Scan(&dueDate) + if err != nil { + return nil, err + } + return &dueDate, nil +} \ No newline at end of file diff --git a/backend/internal/storage/postgres/works.go b/backend/internal/storage/postgres/works.go index 4af2b4f9..330e236e 100644 --- a/backend/internal/storage/postgres/works.go +++ b/backend/internal/storage/postgres/works.go @@ -137,14 +137,14 @@ WHERE student_work_id = $3 return formatted[0], nil } -func (db *DB) GetWorkFromRepoName(ctx context.Context, repoName string) (*models.StudentWork, error){ - var studentWork models.StudentWork - err := db.connPool.QueryRow(ctx, `SELECT * FROM student_works WHERE repo_name = $1`, repoName).Scan(&studentWork) - if err != nil { - return nil, err - } - return &studentWork, nil -} +// func (db *DB) GetWorkFromRepoName(ctx context.Context, repoName string) (*models.StudentWork, error){ +// var studentWork models.StudentWork +// err := db.connPool.QueryRow(ctx, `SELECT * FROM student_works WHERE repo_name = $1`, repoName).Scan(&studentWork) +// if err != nil { +// return nil, err +// } +// return &studentWork, nil +// } func (db *DB) CreateStudentWork(ctx context.Context, assignmentOutlineID int32, gitHubUserID int64, repoName string, workState models.WorkState, dueDate *time.Time) (models.StudentWork, error) { var studentWork models.StudentWork diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index a95ea04d..a7d5a2fb 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -30,8 +30,8 @@ type FeedbackComment interface { type Works interface { GetWorks(ctx context.Context, classroomID int, assignmentID int) ([]*models.StudentWorkWithContributors, error) GetWork(ctx context.Context, classroomID int, assignmentID int, studentWorkID int) (*models.PaginatedStudentWorkWithContributors, error) - GetWorkFromRepoName(ctx context.Context, repoName string) (models.StudentWork, error) CreateStudentWork(ctx context.Context, assignmentOutlineID int32, gitHubUserID int64, repoName string, workState models.WorkState, dueDate *time.Time) (models.StudentWork, error) + GetAssignmentDueDateByRepoName(ctx context.Context, repoName string) (*time.Time, error) } type Test interface { From a8fc053bd9dd08a561b5012428940a2cab1890fe Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:45:03 -0500 Subject: [PATCH 06/14] fully functioning Happy path with deadlines --- backend/internal/errs/api_err.go | 5 +++ .../github/sharedclient/sharedclient.go | 42 ++++--------------- .../classrooms/assignments/assignments.go | 22 +--------- .../handlers/classrooms/classrooms.go | 2 - .../handlers/organizations/organization.go | 2 - .../internal/handlers/webhooks/webhooks.go | 17 ++------ .../storage/postgres/assignment_outlines.go | 2 - .../storage/postgres/assignment_templates.go | 7 ++-- backend/internal/utils/actions.go | 12 ++++-- 9 files changed, 30 insertions(+), 81 deletions(-) diff --git a/backend/internal/errs/api_err.go b/backend/internal/errs/api_err.go index dbc16358..d7be6579 100644 --- a/backend/internal/errs/api_err.go +++ b/backend/internal/errs/api_err.go @@ -98,6 +98,11 @@ func AssignmentNotAcceptedError() APIError { return NewAPIError(http.StatusBadRequest, fmt.Errorf("student has not accepted this assignment yet")) } + +func CriticalGithubError() APIError { + return NewAPIError(http.StatusInternalServerError, fmt.Errorf("Critical Out of State Error: Github Integration")) +} + /* Post Requests Only */ func InvalidRequestBody(expected interface{}) APIError { fieldAcc := make([]string, 0, 10) diff --git a/backend/internal/github/sharedclient/sharedclient.go b/backend/internal/github/sharedclient/sharedclient.go index 5088bc85..bf2d0655 100644 --- a/backend/internal/github/sharedclient/sharedclient.go +++ b/backend/internal/github/sharedclient/sharedclient.go @@ -71,7 +71,6 @@ func (api *CommonAPI) getBranchHead(ctx context.Context, owner, repo, branchName } func (api *CommonAPI) CreateBranch(ctx context.Context, owner, repo, baseBranch, newBranchName string) (*github.Reference, error) { - fmt.Println("Creating branch!!!") endpoint := fmt.Sprintf("/repos/%s/%s/git/refs", owner, repo) // Get the SHA of the base branch @@ -195,28 +194,14 @@ func (api *CommonAPI) createRuleSet(ctx context.Context, ruleset interface{}, or endpoint := fmt.Sprintf("/repos/%s/%s/rulesets", orgName, repoName) req, err := api.Client.NewRequest("POST", endpoint, ruleset) if err != nil { - fmt.Printf("Request Construction failed") return err } - - resp, err := api.Client.Do(ctx, req, nil) - if err != nil { - fmt.Printf("request execution failed\n\n") - fmt.Printf("Request:%v\n\n", req) - fmt.Printf("Response: %v\n\n", resp) - fmt.Printf("Error: %v\n\n", err) - return err - } - - return nil - - + _, err = api.Client.Do(ctx, req, nil) + return err } //Given a repo name and org name, create a push ruleset to protect the .github directory func (api *CommonAPI) CreatePushRuleset(ctx context.Context, orgName, repoName string) error { - //fmt.Println("Creating Push Ruleset") - body := map[string]interface{}{ "name": "Restrict .github Directory Edits: Preserves Submission Deadline", "target": "push", @@ -236,10 +221,8 @@ func (api *CommonAPI) CreatePushRuleset(ctx context.Context, orgName, repoName s func (api *CommonAPI) CreateBranchRuleset(ctx context.Context, orgName, repoName string) error { - //fmt.Println("Creating Branch Ruleset") - body := map[string]interface{}{ - "name": "protect feedback from edits", + "name": "Feedback and Main Branch Protedtion: PR Enforcement", "target": "branch", "enforcement": "active", "conditions": map[string]interface{}{ @@ -282,7 +265,6 @@ func (api *CommonAPI) CreateBranchRuleset(ctx context.Context, orgName, repoNam }, }, } - return api.createRuleSet(ctx, body, orgName, repoName) } @@ -296,7 +278,7 @@ func (api *CommonAPI) CreateDeadlineEnforcement(ctx context.Context, deadline *t RepoName: repoName, OwnerName: orgName, DestinationBranch: "main", - Content: utils.ActionWithDeadlineStub(), + Content: utils.ActionWithDeadline(deadline), CommitMessage: "Deadline enforcement GH action files", } return api.EditRepository(ctx, &addition) @@ -319,7 +301,6 @@ func (api *CommonAPI) CreatePREnforcement(ctx context.Context, orgName, repoName } func (api *CommonAPI) EditRepository(ctx context.Context, addition *models.RepositoryAddition) error { - fmt.Println("Reached function correctly- Repository Edit\n\n") endpoint := fmt.Sprintf("/repos/%s/%s/contents/%s", addition.OwnerName, addition.RepoName, addition.FilePath) encodedContent := base64.StdEncoding.EncodeToString([]byte(addition.Content)) @@ -328,21 +309,16 @@ func (api *CommonAPI) EditRepository(ctx context.Context, addition *models.Repos "content": encodedContent, "branch": addition.DestinationBranch, } - req, err := api.Client.NewRequest("PUT", endpoint, body) - fmt.Println("Body: ", req) if err != nil { - fmt.Printf("Request Construction failed: %s", addition.CommitMessage) + return err } - _, err = api.Client.Do(ctx, req, nil) - if err != nil { - fmt.Printf("request execution failed: %s", addition.CommitMessage) - fmt.Println(err.Error()) - } - - return nil + return err } + + + func (api *CommonAPI) InviteUserToOrganization(ctx context.Context, orgName string, userID int64) error { body := map[string]interface{}{ "invitee_id": userID, diff --git a/backend/internal/handlers/classrooms/assignments/assignments.go b/backend/internal/handlers/classrooms/assignments/assignments.go index 7b0ce38a..2a9a8d84 100644 --- a/backend/internal/handlers/classrooms/assignments/assignments.go +++ b/backend/internal/handlers/classrooms/assignments/assignments.go @@ -2,8 +2,6 @@ package assignments import ( "errors" - "fmt" - //"fmt" "net/http" "strconv" "strings" @@ -56,11 +54,7 @@ func (s *AssignmentService) getAssignment() fiber.Handler { } func (s *AssignmentService) createAssignment() fiber.Handler { - //TODO: .github folder addition - //TODO: push rulesets - //TODO: Branch protections (maybe) return func(c *fiber.Ctx) error { - fmt.Println("Creating Assignment/n/n") // Parse request body var assignmentData models.AssignmentOutline error := c.BodyParser(&assignmentData) @@ -106,19 +100,6 @@ func (s *AssignmentService) createAssignment() fiber.Handler { return err } - // //Create deadline enforcement if one is given - // if assignmentData.MainDueDate != nil { - - // err = s.appClient.CreatePushRuleset(c.Context(), classroom.OrgName, baseRepoName) - // if err != nil { - // fmt.Println("Skill Issue #2") - // } - // err = s.appClient.CreateBranchRuleset(c.Context(), classroom.OrgName, baseRepoName) - // if err != nil { - // fmt.Println("Skill Issue #3") - // } - // } - return c.Status(http.StatusOK).JSON(fiber.Map{ "created_assignment": createdAssignment, }) @@ -249,8 +230,7 @@ func (s *AssignmentService) useAssignmentToken() fiber.Handler { //@CamTODO: Get rid of happy path here with repo get fail err = client.CreateBranchRuleset(c.Context(), classroom.OrgName, forkName) if err != nil { - fmt.Printf("Error creating branch ruleset\n\n") - fmt.Println(err.Error()) + return errs.CriticalGithubError() } // Remove student team's access to forked repo diff --git a/backend/internal/handlers/classrooms/classrooms.go b/backend/internal/handlers/classrooms/classrooms.go index b3e8da43..b676fd9a 100644 --- a/backend/internal/handlers/classrooms/classrooms.go +++ b/backend/internal/handlers/classrooms/classrooms.go @@ -2,7 +2,6 @@ package classrooms import ( "context" - "fmt" "net/http" "strconv" "strings" @@ -391,7 +390,6 @@ func (s *ClassroomService) getCurrentClassroomUser() fiber.Handler { } if classroomUser.Status == models.UserStatusNotInOrg { - fmt.Println("Error correctly identified") return errs.InconsistentOrgMembershipError() } diff --git a/backend/internal/handlers/organizations/organization.go b/backend/internal/handlers/organizations/organization.go index 92a04282..ebe55a36 100644 --- a/backend/internal/handlers/organizations/organization.go +++ b/backend/internal/handlers/organizations/organization.go @@ -46,14 +46,12 @@ func (service *OrganizationService) GetInstalledOrgs() fiber.Handler { // Get the list of organizations the user is part of userOrgs, err := userClient.GetUserOrgs(c.Context()) - //fmt.Println(userOrgs) if err != nil { return errs.GithubAPIError(err) } // Get the list of installations of the GitHub app appInstallations, err := appClient.ListInstallations(c.Context()) - //fmt.Println(appInstallations) if err != nil { return errs.GithubAPIError(err) } diff --git a/backend/internal/handlers/webhooks/webhooks.go b/backend/internal/handlers/webhooks/webhooks.go index eda077c9..8076a852 100644 --- a/backend/internal/handlers/webhooks/webhooks.go +++ b/backend/internal/handlers/webhooks/webhooks.go @@ -1,11 +1,7 @@ package webhooks import ( - //"encoding/json" "errors" - "fmt" - //"time" - "github.com/CamPlume1/khoury-classroom/internal/errs" models "github.com/CamPlume1/khoury-classroom/internal/models" "github.com/gofiber/fiber/v2" @@ -51,8 +47,6 @@ func (s *WebHookService) PRThread(c *fiber.Ctx) error { } func (s *WebHookService) PushEvent(c *fiber.Ctx) error { - fmt.Println("Push Event Triggered") - fmt.Println(c.AllParams()) // Extract the 'payload' form value pushEvent := github.PushEvent{} if err := c.BodyParser(&pushEvent); err != nil { @@ -87,12 +81,11 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P // There is a deadline err = s.appClient.CreateDeadlineEnforcement(c.Context(), deadline, *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { - fmt.Println(err) - fmt.Println("DEADLINE ENFORCEMENT FAILED") + //TODO: Recovery. Will do in a new ticket. For now, log + return err } - } - + //Create PR Enforcement Action err = s.appClient.CreatePREnforcement(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { return err @@ -116,7 +109,6 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P err = s.appClient.CreatePushRuleset(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { // TODO: Recovery. Will do in a new ticket. For now, log - fmt.Errorf(err.Error()) return err } @@ -124,13 +116,11 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P assignmentOutline, err := s.store.GetAssignmentByBaseRepoID(c.Context(), *pushEvent.Repo.ID) if err != nil { // TODO: Recovery. Will do in a new ticket. For now, log - fmt.Errorf(err.Error()) return err } classroom, err := s.store.GetClassroomByID(c.Context(), assignmentOutline.ClassroomID) if err != nil { // TODO: Recovery. Will do in a new ticket. For now, log - fmt.Errorf(err.Error()) return err } @@ -139,7 +129,6 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "pull") if err != nil { // TODO: Recovery. Will do in a new ticket. For now, log - fmt.Errorf(err.Error()) return err } diff --git a/backend/internal/storage/postgres/assignment_outlines.go b/backend/internal/storage/postgres/assignment_outlines.go index 5bd69ac5..ef79547c 100644 --- a/backend/internal/storage/postgres/assignment_outlines.go +++ b/backend/internal/storage/postgres/assignment_outlines.go @@ -2,7 +2,6 @@ package postgres import ( "context" - "github.com/CamPlume1/khoury-classroom/internal/errs" "github.com/CamPlume1/khoury-classroom/internal/models" "github.com/jackc/pgx/v5" @@ -125,7 +124,6 @@ func (db *DB) CreateAssignment(ctx context.Context, assignmentRequestData models if err != nil { return assignmentOutline, errs.NewDBError(err) } - return assignmentOutline, nil } diff --git a/backend/internal/storage/postgres/assignment_templates.go b/backend/internal/storage/postgres/assignment_templates.go index 3272153d..9f805bea 100644 --- a/backend/internal/storage/postgres/assignment_templates.go +++ b/backend/internal/storage/postgres/assignment_templates.go @@ -2,6 +2,7 @@ package postgres import ( "context" + "strings" "time" "github.com/CamPlume1/khoury-classroom/internal/errs" @@ -23,7 +24,7 @@ func (db *DB) CreateAssignmentTemplate(ctx context.Context, assignmentTemplateDa VALUES ($1, $2, $3) RETURNING *`, assignmentTemplateData.TemplateRepoOwner, - assignmentTemplateData.TemplateRepoName, + strings.ToLower(assignmentTemplateData.TemplateRepoName), assignmentTemplateData.TemplateID, ).Scan(&assignmentTemplateData.TemplateID, &assignmentTemplateData.TemplateRepoOwner, @@ -56,8 +57,8 @@ func (db *DB) GetAssignmentDueDateByRepoName(ctx context.Context, repoName strin var dueDate time.Time err := db.connPool.QueryRow(ctx, `SELECT ao.main_due_date FROM assignment_outlines ao - JOIN assignment_templates at ON ao.template_id = at.template_repo_id - WHERE at.template_repo_name = 'your_template_repo_name';`, repoName).Scan(&dueDate) + JOIN assignment_base_repos at ON ao.base_repo_id = at.base_repo_id + WHERE at.base_repo_name ILIKE $1;`, strings.ToLower(repoName)).Scan(&dueDate) if err != nil { return nil, err } diff --git a/backend/internal/utils/actions.go b/backend/internal/utils/actions.go index 3184b73e..e1aa3d56 100644 --- a/backend/internal/utils/actions.go +++ b/backend/internal/utils/actions.go @@ -1,15 +1,19 @@ package utils -import "fmt" +import ( + "fmt" + "time" +) -func ActionWithDeadlineStub() string { +func ActionWithDeadline(deadline *time.Time) string { + // yyyy, mm, dd, hh, mm, ss var scriptString = ` from datetime import datetime import sys def check_date(): # Define the target date - target_date = datetime(2024, 12, 20, 0, 0, 0, tzinfo=timezone.utc) + target_date = datetime(%d, %d, %d, %d, %d, %d, tzinfo=timezone.utc) # Get current date and time current_date = datetime.now() @@ -23,7 +27,7 @@ def check_date(): if __name__ == "__main__": check_date()` - return fmt.Sprintf(scriptString) + return fmt.Sprintf(scriptString, deadline.Year(), deadline.Month(), deadline.Day(), deadline.Hour(), deadline.Minute(), deadline.Second()) } From 2dec48b44d55c61ddd4472d0ab932445cc888e8b Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:58:03 -0500 Subject: [PATCH 07/14] fixing styling stuff --- backend/internal/handlers/auth/auth.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/backend/internal/handlers/auth/auth.go b/backend/internal/handlers/auth/auth.go index a5df7754..a4a3019f 100644 --- a/backend/internal/handlers/auth/auth.go +++ b/backend/internal/handlers/auth/auth.go @@ -1,10 +1,6 @@ package auth import ( -<<<<<<< HEAD - //"fmt" -======= ->>>>>>> main "net/url" "strconv" "strings" From d0f3767fe5371b9101378565d1aad02ef283767e Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:01:59 -0500 Subject: [PATCH 08/14] capitalization fix --- backend/internal/errs/api_err.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/errs/api_err.go b/backend/internal/errs/api_err.go index d01f3062..7a4428ef 100644 --- a/backend/internal/errs/api_err.go +++ b/backend/internal/errs/api_err.go @@ -108,7 +108,7 @@ func AssignmentNotAcceptedError() APIError { func CriticalGithubError() APIError { - return NewAPIError(http.StatusInternalServerError, fmt.Errorf("Critical Out of State Error: Github Integration")) + return NewAPIError(http.StatusInternalServerError, fmt.Errorf("critical Out of State Error: Github Integration")) } /* Post Requests Only */ From c49635de4da1fe36122787ed2728393a713c15ff Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:05:47 -0500 Subject: [PATCH 09/14] self review --- backend/internal/github/github.go | 3 --- backend/internal/github/sharedclient/sharedclient.go | 3 --- 2 files changed, 6 deletions(-) diff --git a/backend/internal/github/github.go b/backend/internal/github/github.go index a9d532c2..33f67a14 100644 --- a/backend/internal/github/github.go +++ b/backend/internal/github/github.go @@ -55,9 +55,6 @@ type GitHubUserClient interface { // All methods in the OAUTH client GetCurrUserOrgMembership(ctx context.Context, orgName string) (*github.Membership, error) ForkRepository(ctx context.Context, org, owner, repo, destName string) error - - // Create Branch protections for a given repo in an org - //CreatePushRuleset(ctx context.Context, orgName, repoName string) error } type GitHubBaseClient interface { //All methods in the SHARED client diff --git a/backend/internal/github/sharedclient/sharedclient.go b/backend/internal/github/sharedclient/sharedclient.go index cac9e611..d316d8d3 100644 --- a/backend/internal/github/sharedclient/sharedclient.go +++ b/backend/internal/github/sharedclient/sharedclient.go @@ -3,11 +3,8 @@ package sharedclient import ( "context" "encoding/base64" - - //"encoding/json" "fmt" "time" - "github.com/CamPlume1/khoury-classroom/internal/errs" "github.com/CamPlume1/khoury-classroom/internal/models" "github.com/CamPlume1/khoury-classroom/internal/utils" From 5c38e2d69f8a23cd904a372890ead5c9d2cacc56 Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:37:53 -0500 Subject: [PATCH 10/14] added update and deletion branch changes --- backend/internal/github/sharedclient/sharedclient.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/internal/github/sharedclient/sharedclient.go b/backend/internal/github/sharedclient/sharedclient.go index d316d8d3..e23c9702 100644 --- a/backend/internal/github/sharedclient/sharedclient.go +++ b/backend/internal/github/sharedclient/sharedclient.go @@ -232,6 +232,16 @@ func (api *CommonAPI) CreateBranchRuleset(ctx context.Context, orgName, repoNam map[string]interface{}{ "type": "non_fast_forward", }, + map[string]interface{}{ + "type": "deletion", + }, + map[string]interface{}{ + "type": "update", + "parameters": map[string]interface{}{ + "update_allows_fetch_and_merge": true, + }, + }, + map[string]interface{}{ "type": "pull_request", "parameters" : map[string]interface{}{ From 562739c62f3b1b659528e47858e71a3c66e7bfb4 Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:58:08 -0500 Subject: [PATCH 11/14] Incorporating PR Feedback --- backend/internal/github/github.go | 2 +- .../github/sharedclient/sharedclient.go | 4 +-- .../handlers/organizations/organization.go | 1 - .../internal/handlers/webhooks/webhooks.go | 25 +++++++++++-------- .../storage/postgres/assignment_templates.go | 11 ++++---- backend/internal/storage/postgres/works.go | 9 ------- backend/internal/storage/storage.go | 3 ++- 7 files changed, 25 insertions(+), 30 deletions(-) diff --git a/backend/internal/github/github.go b/backend/internal/github/github.go index 33f67a14..89e75921 100644 --- a/backend/internal/github/github.go +++ b/backend/internal/github/github.go @@ -30,7 +30,7 @@ type GitHubAppClient interface { // All methods in the APP client // Add a repository permission to a user AssignPermissionToUser(ctx context.Context, ownerName string, repoName string, userName string, permission string) error - CreateDeadlineEnforcement(ctx context.Context, deadline *time.Time, orgName, repoName string) error + CreateDeadlineEnforcement(ctx context.Context, deadline *time.Time, orgName, repoName, branchName string) error // Create instance of template repository CreateRepoFromTemplate(ctx context.Context, orgName, templateRepoName, newRepoName string) (*models.AssignmentBaseRepo, error) diff --git a/backend/internal/github/sharedclient/sharedclient.go b/backend/internal/github/sharedclient/sharedclient.go index e23c9702..e21b62fe 100644 --- a/backend/internal/github/sharedclient/sharedclient.go +++ b/backend/internal/github/sharedclient/sharedclient.go @@ -279,12 +279,12 @@ func (api *CommonAPI) CreateBranchRuleset(ctx context.Context, orgName, repoNam -func (api *CommonAPI) CreateDeadlineEnforcement(ctx context.Context, deadline *time.Time, orgName, repoName string) error { +func (api *CommonAPI) CreateDeadlineEnforcement(ctx context.Context, deadline *time.Time, orgName, repoName, branchName string) error { addition := models.RepositoryAddition{ FilePath: ".github/workflows/deadline.yml", RepoName: repoName, OwnerName: orgName, - DestinationBranch: "main", + DestinationBranch: branchName, Content: utils.ActionWithDeadline(deadline), CommitMessage: "Deadline enforcement GH action files", } diff --git a/backend/internal/handlers/organizations/organization.go b/backend/internal/handlers/organizations/organization.go index 915ee58b..b237d290 100644 --- a/backend/internal/handlers/organizations/organization.go +++ b/backend/internal/handlers/organizations/organization.go @@ -1,7 +1,6 @@ package organizations import ( - //"fmt" "strconv" "strings" diff --git a/backend/internal/handlers/webhooks/webhooks.go b/backend/internal/handlers/webhooks/webhooks.go index 419e91d6..27d2d28d 100644 --- a/backend/internal/handlers/webhooks/webhooks.go +++ b/backend/internal/handlers/webhooks/webhooks.go @@ -91,15 +91,21 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P } // Retrieve assignment deadline from DB - deadline, err := s.store.GetAssignmentDueDateByRepoName(c.Context(), *pushEvent.Repo.Name) - if err == nil { + template, err := s.store.GetAssignmentByRepoName(c.Context(), *pushEvent.Repo.Name) + if err != nil { + //@KHO-239 + return err + } + if template.MainDueDate != nil { // There is a deadline - err = s.appClient.CreateDeadlineEnforcement(c.Context(), deadline, *pushEvent.Repo.Organization, *pushEvent.Repo.Name) + err = s.appClient.CreateDeadlineEnforcement(c.Context(), template.MainDueDate, *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "main") if err != nil { - //TODO: Recovery. Will do in a new ticket. For now, log + //@KHO-239 return err } - } + } + + //Create PR Enforcement Action err = s.appClient.CreatePREnforcement(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { @@ -123,19 +129,19 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P err = s.appClient.CreatePushRuleset(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { - // TODO: Recovery. Will do in a new ticket. For now, log + // @KHO-239 return err } // Find the associated assignment and classroom assignmentOutline, err := s.store.GetAssignmentByBaseRepoID(c.Context(), *pushEvent.Repo.ID) if err != nil { - // TODO: Recovery. Will do in a new ticket. For now, log + // @KHO-239 return err } classroom, err := s.store.GetClassroomByID(c.Context(), assignmentOutline.ClassroomID) if err != nil { - // TODO: Recovery. Will do in a new ticket. For now, log + // @KHO-239 return err } @@ -143,7 +149,7 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P err = s.appClient.UpdateTeamRepoPermissions(c.Context(), *pushEvent.Repo.Organization, *classroom.StudentTeamName, *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "pull") if err != nil { - // TODO: Recovery. Will do in a new ticket. For now, log + // @KHO-239 return err } @@ -164,7 +170,6 @@ func (s *WebHookService) updateWorkStateOnStudentCommit(c *fiber.Ctx, pushEvent } if pushEvent.Ref != nil { - // TODO: Dynamically determine branch names once parameterized // If commiting to main branch, mark as submitted if *pushEvent.Ref == "refs/heads/main" { studentWork.WorkState = models.WorkStateSubmitted diff --git a/backend/internal/storage/postgres/assignment_templates.go b/backend/internal/storage/postgres/assignment_templates.go index 9f805bea..bde99186 100644 --- a/backend/internal/storage/postgres/assignment_templates.go +++ b/backend/internal/storage/postgres/assignment_templates.go @@ -3,7 +3,6 @@ package postgres import ( "context" "strings" - "time" "github.com/CamPlume1/khoury-classroom/internal/errs" "github.com/CamPlume1/khoury-classroom/internal/models" @@ -53,14 +52,14 @@ func (db *DB) GetAssignmentTemplateByID(ctx context.Context, templateID int64) ( return assignmentTemplate, nil } -func (db *DB) GetAssignmentDueDateByRepoName(ctx context.Context, repoName string) (*time.Time, error){ - var dueDate time.Time - err := db.connPool.QueryRow(ctx, `SELECT ao.main_due_date +func (db *DB) GetAssignmentByRepoName(ctx context.Context, repoName string) (*models.AssignmentOutline, error){ + var assignment models.AssignmentOutline + err := db.connPool.QueryRow(ctx, `SELECT * FROM assignment_outlines ao JOIN assignment_base_repos at ON ao.base_repo_id = at.base_repo_id - WHERE at.base_repo_name ILIKE $1;`, strings.ToLower(repoName)).Scan(&dueDate) + WHERE at.base_repo_name ILIKE $1;`, strings.ToLower(repoName)).Scan(&assignment) if err != nil { return nil, err } - return &dueDate, nil + return &assignment, nil } \ No newline at end of file diff --git a/backend/internal/storage/postgres/works.go b/backend/internal/storage/postgres/works.go index 3c92d110..ceb6651c 100644 --- a/backend/internal/storage/postgres/works.go +++ b/backend/internal/storage/postgres/works.go @@ -139,15 +139,6 @@ WHERE student_work_id = $3 return formatted[0], nil } -// func (db *DB) GetWorkFromRepoName(ctx context.Context, repoName string) (*models.StudentWork, error){ -// var studentWork models.StudentWork -// err := db.connPool.QueryRow(ctx, `SELECT * FROM student_works WHERE repo_name = $1`, repoName).Scan(&studentWork) -// if err != nil { -// return nil, err -// } -// return &studentWork, nil -// } - func (db *DB) CreateStudentWork(ctx context.Context, assignmentOutlineID int32, gitHubUserID int64, repoName string, workState models.WorkState, dueDate *time.Time) (models.StudentWork, error) { var studentWork models.StudentWork diff --git a/backend/internal/storage/storage.go b/backend/internal/storage/storage.go index 2773e980..2b0fb654 100644 --- a/backend/internal/storage/storage.go +++ b/backend/internal/storage/storage.go @@ -31,7 +31,7 @@ type Works interface { GetWorks(ctx context.Context, classroomID int, assignmentID int) ([]*models.StudentWorkWithContributors, error) GetWork(ctx context.Context, classroomID int, assignmentID int, studentWorkID int) (*models.PaginatedStudentWorkWithContributors, error) CreateStudentWork(ctx context.Context, assignmentOutlineID int32, gitHubUserID int64, repoName string, workState models.WorkState, dueDate *time.Time) (models.StudentWork, error) - GetAssignmentDueDateByRepoName(ctx context.Context, repoName string) (*time.Time, error) + UpdateStudentWork(ctx context.Context, UpdateStudentWork models.StudentWork) (models.StudentWork, error) GetWorkByRepoName(ctx context.Context, repoName string) (models.StudentWork, error) } @@ -81,6 +81,7 @@ type AssignmentOutline interface { GetTotalWorkCommits(ctx context.Context, assignmentID int) (int, error) GetAssignmentByToken(ctx context.Context, token string) (models.AssignmentOutline, error) CreateAssignmentToken(ctx context.Context, tokenData models.AssignmentToken) (models.AssignmentToken, error) + GetAssignmentByRepoName(ctx context.Context, repoName string) (*models.AssignmentOutline, error) } type AssignmentTemplate interface { From bcec9549cdb70ccb2604a43daaf1323214936782 Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:11:55 -0500 Subject: [PATCH 12/14] fixing someone's crud bug (maybe mine) --- .../internal/handlers/webhooks/webhooks.go | 28 +++++++++---------- .../storage/postgres/assignment_templates.go | 18 ++++++++---- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/backend/internal/handlers/webhooks/webhooks.go b/backend/internal/handlers/webhooks/webhooks.go index 97daf64d..4d771121 100644 --- a/backend/internal/handlers/webhooks/webhooks.go +++ b/backend/internal/handlers/webhooks/webhooks.go @@ -2,7 +2,9 @@ package webhooks import ( "errors" + "fmt" "strings" + "github.com/CamPlume1/khoury-classroom/internal/errs" models "github.com/CamPlume1/khoury-classroom/internal/models" "github.com/gofiber/fiber/v2" @@ -49,24 +51,12 @@ func (s *WebHookService) PRThread(c *fiber.Ctx) error { } func (s *WebHookService) PushEvent(c *fiber.Ctx) error { -<<<<<<< HEAD // Extract the 'payload' form value pushEvent := github.PushEvent{} if err := c.BodyParser(&pushEvent); err != nil { return err } - // Check if this is first commit in a repository - isInitialCommit, err := s.isInitialCommit(pushEvent) - if err != nil { -======= - // Unmarshal the JSON payload into the PushEvent struct - pushEvent := github.PushEvent{} - if err := c.BodyParser(&pushEvent); err != nil { ->>>>>>> main - return err - } - // If app bot triggered the initial commit, initialize the base repository if isInitialCommit(pushEvent) && isBotPushEvent(pushEvent) { err := s.baseRepoInitialization(c, pushEvent) @@ -94,6 +84,7 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P // Retrieve assignment deadline from DB template, err := s.store.GetAssignmentByRepoName(c.Context(), *pushEvent.Repo.Name) if err != nil { + fmt.Println("Crud error 1") //@KHO-239 return err } @@ -102,6 +93,7 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P err = s.appClient.CreateDeadlineEnforcement(c.Context(), template.MainDueDate, *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "main") if err != nil { //@KHO-239 + fmt.Println("Crud error 2") return err } } @@ -110,6 +102,7 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P //Create PR Enforcement Action err = s.appClient.CreatePREnforcement(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { + fmt.Println("Crud error 3") return err } @@ -130,12 +123,14 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P err = s.appClient.CreatePushRuleset(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { // @KHO-239 + fmt.Println("Crud error 4") return err } - + // Create empty commit (will create a diff that allows feedback PR to be created) - err := s.appClient.CreateEmptyCommit(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) + err = s.appClient.CreateEmptyCommit(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { + fmt.Println("Crud error 5") return errs.InternalServerError() } @@ -143,11 +138,13 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P assignmentOutline, err := s.store.GetAssignmentByBaseRepoID(c.Context(), *pushEvent.Repo.ID) if err != nil { // @KHO-239 + fmt.Println("Crud error 6") return err } classroom, err := s.store.GetClassroomByID(c.Context(), assignmentOutline.ClassroomID) if err != nil { // @KHO-239 + fmt.Println("Crud error 7") return err } @@ -156,6 +153,7 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "pull") if err != nil { // @KHO-239 + fmt.Println("Crud error 8") return err } @@ -166,6 +164,7 @@ func (s *WebHookService) updateWorkStateOnStudentCommit(c *fiber.Ctx, pushEvent // Find the associated student work studentWork, err := s.store.GetWorkByRepoName(c.Context(), *pushEvent.Repo.Name) if err != nil { + fmt.Println("Crud error 9") return err } @@ -188,6 +187,7 @@ func (s *WebHookService) updateWorkStateOnStudentCommit(c *fiber.Ctx, pushEvent // Store updated student work locally _, err = s.store.UpdateStudentWork(c.Context(), studentWork) if err != nil { + fmt.Println("Crud error 10") return errs.InternalServerError() } diff --git a/backend/internal/storage/postgres/assignment_templates.go b/backend/internal/storage/postgres/assignment_templates.go index bde99186..e1db7bea 100644 --- a/backend/internal/storage/postgres/assignment_templates.go +++ b/backend/internal/storage/postgres/assignment_templates.go @@ -6,6 +6,7 @@ import ( "github.com/CamPlume1/khoury-classroom/internal/errs" "github.com/CamPlume1/khoury-classroom/internal/models" + "github.com/jackc/pgx/v5" ) func (db *DB) AssignmentTemplateExists(ctx context.Context, templateID int64) (bool, error) { @@ -53,13 +54,18 @@ func (db *DB) GetAssignmentTemplateByID(ctx context.Context, templateID int64) ( } func (db *DB) GetAssignmentByRepoName(ctx context.Context, repoName string) (*models.AssignmentOutline, error){ - var assignment models.AssignmentOutline - err := db.connPool.QueryRow(ctx, `SELECT * - FROM assignment_outlines ao - JOIN assignment_base_repos at ON ao.base_repo_id = at.base_repo_id - WHERE at.base_repo_name ILIKE $1;`, strings.ToLower(repoName)).Scan(&assignment) + row, err := db.connPool.Query(ctx, `SELECT * + FROM assignment_outlines ao + JOIN assignment_base_repos at ON ao.base_repo_id = at.base_repo_id + WHERE at.base_repo_name ILIKE $1;`, strings.ToLower(repoName)) + if err != nil { return nil, err } - return &assignment, nil + + outline, err := pgx.RowToStructByName[models.AssignmentOutline](row) + if err != nil { + return nil, err + } + return &outline, nil } \ No newline at end of file From 9cd3763c45a53e1d846b67595b3c6ffeccf890fa Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:39:33 -0500 Subject: [PATCH 13/14] fully working --- .../internal/handlers/webhooks/webhooks.go | 9 ------ backend/internal/models/assignment_outline.go | 4 +-- .../storage/postgres/assignment_outlines.go | 28 +++++++++++++++++++ .../storage/postgres/assignment_templates.go | 21 +------------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/backend/internal/handlers/webhooks/webhooks.go b/backend/internal/handlers/webhooks/webhooks.go index 4d771121..981a6f1e 100644 --- a/backend/internal/handlers/webhooks/webhooks.go +++ b/backend/internal/handlers/webhooks/webhooks.go @@ -84,7 +84,6 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P // Retrieve assignment deadline from DB template, err := s.store.GetAssignmentByRepoName(c.Context(), *pushEvent.Repo.Name) if err != nil { - fmt.Println("Crud error 1") //@KHO-239 return err } @@ -93,7 +92,6 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P err = s.appClient.CreateDeadlineEnforcement(c.Context(), template.MainDueDate, *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "main") if err != nil { //@KHO-239 - fmt.Println("Crud error 2") return err } } @@ -102,7 +100,6 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P //Create PR Enforcement Action err = s.appClient.CreatePREnforcement(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { - fmt.Println("Crud error 3") return err } @@ -123,14 +120,12 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P err = s.appClient.CreatePushRuleset(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { // @KHO-239 - fmt.Println("Crud error 4") return err } // Create empty commit (will create a diff that allows feedback PR to be created) err = s.appClient.CreateEmptyCommit(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) if err != nil { - fmt.Println("Crud error 5") return errs.InternalServerError() } @@ -138,13 +133,11 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P assignmentOutline, err := s.store.GetAssignmentByBaseRepoID(c.Context(), *pushEvent.Repo.ID) if err != nil { // @KHO-239 - fmt.Println("Crud error 6") return err } classroom, err := s.store.GetClassroomByID(c.Context(), assignmentOutline.ClassroomID) if err != nil { // @KHO-239 - fmt.Println("Crud error 7") return err } @@ -153,7 +146,6 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "pull") if err != nil { // @KHO-239 - fmt.Println("Crud error 8") return err } @@ -164,7 +156,6 @@ func (s *WebHookService) updateWorkStateOnStudentCommit(c *fiber.Ctx, pushEvent // Find the associated student work studentWork, err := s.store.GetWorkByRepoName(c.Context(), *pushEvent.Repo.Name) if err != nil { - fmt.Println("Crud error 9") return err } diff --git a/backend/internal/models/assignment_outline.go b/backend/internal/models/assignment_outline.go index 6c679494..d03c4f92 100644 --- a/backend/internal/models/assignment_outline.go +++ b/backend/internal/models/assignment_outline.go @@ -14,9 +14,9 @@ type AssignmentTokenRequestBody struct { } type AssignmentOutline struct { - ID int32 `json:"id,omitempty"` + ID int32 `json:"id,omitempty" db:"id"` TemplateID int64 `json:"template_id,omitempty"` - BaseRepoID int64 `json:"base_repo_id,omitempty"` + BaseRepoID int64 `json:"base_repo_id,omitempty" db:"base_repo_id"` CreatedAt time.Time `json:"created_at,omitempty"` ReleasedAt *time.Time `json:"released_at,omitempty"` Name string `json:"name"` diff --git a/backend/internal/storage/postgres/assignment_outlines.go b/backend/internal/storage/postgres/assignment_outlines.go index 1905de56..72beed8b 100644 --- a/backend/internal/storage/postgres/assignment_outlines.go +++ b/backend/internal/storage/postgres/assignment_outlines.go @@ -2,7 +2,10 @@ package postgres import ( "context" + "fmt" + "strings" "time" + "github.com/CamPlume1/khoury-classroom/internal/errs" "github.com/CamPlume1/khoury-classroom/internal/models" "github.com/jackc/pgx/v5" @@ -265,3 +268,28 @@ func (db *DB) CountWorksByState(ctx context.Context, assignmentID int) (map[mode return workStateCounts, nil } + +func (db *DB) GetAssignmentByRepoName(ctx context.Context, repoName string) (*models.AssignmentOutline, error){ + var outline models.AssignmentOutline + row := db.connPool.QueryRow(ctx, `SELECT ao.id, ao.template_id, ao.base_repo_id, ao.classroom_id, ao.created_at, ao.released_at, ao.name, ao.rubric_id, ao.group_assignment, ao.default_score, ao.main_due_date + FROM assignment_outlines ao + JOIN assignment_base_repos at ON ao.base_repo_id = at.base_repo_id + WHERE at.base_repo_name ILIKE $1;`, strings.ToLower(repoName)) + err := row.Scan(&outline.ID, + &outline.TemplateID, + &outline.BaseRepoID, + &outline.ClassroomID, + &outline.CreatedAt, + &outline.ReleasedAt, + &outline.Name, + &outline.RubricID, + &outline.GroupAssignment, + &outline.DefaultScore, + &outline.MainDueDate) + if err != nil { + fmt.Println("oof") + fmt.Println(err) + return nil, err + } + return &outline, nil +} \ No newline at end of file diff --git a/backend/internal/storage/postgres/assignment_templates.go b/backend/internal/storage/postgres/assignment_templates.go index e1db7bea..b2f8d91f 100644 --- a/backend/internal/storage/postgres/assignment_templates.go +++ b/backend/internal/storage/postgres/assignment_templates.go @@ -2,11 +2,9 @@ package postgres import ( "context" - "strings" "github.com/CamPlume1/khoury-classroom/internal/errs" "github.com/CamPlume1/khoury-classroom/internal/models" - "github.com/jackc/pgx/v5" ) func (db *DB) AssignmentTemplateExists(ctx context.Context, templateID int64) (bool, error) { @@ -24,7 +22,7 @@ func (db *DB) CreateAssignmentTemplate(ctx context.Context, assignmentTemplateDa VALUES ($1, $2, $3) RETURNING *`, assignmentTemplateData.TemplateRepoOwner, - strings.ToLower(assignmentTemplateData.TemplateRepoName), + assignmentTemplateData.TemplateRepoName, assignmentTemplateData.TemplateID, ).Scan(&assignmentTemplateData.TemplateID, &assignmentTemplateData.TemplateRepoOwner, @@ -51,21 +49,4 @@ func (db *DB) GetAssignmentTemplateByID(ctx context.Context, templateID int64) ( } return assignmentTemplate, nil -} - -func (db *DB) GetAssignmentByRepoName(ctx context.Context, repoName string) (*models.AssignmentOutline, error){ - row, err := db.connPool.Query(ctx, `SELECT * - FROM assignment_outlines ao - JOIN assignment_base_repos at ON ao.base_repo_id = at.base_repo_id - WHERE at.base_repo_name ILIKE $1;`, strings.ToLower(repoName)) - - if err != nil { - return nil, err - } - - outline, err := pgx.RowToStructByName[models.AssignmentOutline](row) - if err != nil { - return nil, err - } - return &outline, nil } \ No newline at end of file From 9561288572c5cc82be01ab107cf72bc39675e759 Mon Sep 17 00:00:00 2001 From: Cam Plume <116120547+CamPlume1@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:45:58 -0500 Subject: [PATCH 14/14] final tweaks --- backend/internal/github/github.go | 2 +- backend/internal/github/sharedclient/sharedclient.go | 4 ++-- .../internal/handlers/classrooms/assignments/assignments.go | 4 ++-- backend/internal/handlers/webhooks/webhooks.go | 4 +--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/backend/internal/github/github.go b/backend/internal/github/github.go index 7be7eec7..9f1aafbf 100644 --- a/backend/internal/github/github.go +++ b/backend/internal/github/github.go @@ -131,7 +131,7 @@ type GitHubBaseClient interface { //All methods in the SHARED client CreateBranchRuleset(ctx context.Context, orgName, repoName string) error //Creates PR enforcements - CreatePREnforcement(ctx context.Context, orgName, repoName string) error + CreatePREnforcement(ctx context.Context, orgName, repoName, branchName string) error // Create empty commit (will create a diff that allows feedback PR to be created) CreateEmptyCommit(ctx context.Context, owner, repo string) error diff --git a/backend/internal/github/sharedclient/sharedclient.go b/backend/internal/github/sharedclient/sharedclient.go index 987dca5a..4540e67a 100644 --- a/backend/internal/github/sharedclient/sharedclient.go +++ b/backend/internal/github/sharedclient/sharedclient.go @@ -293,13 +293,13 @@ func (api *CommonAPI) CreateDeadlineEnforcement(ctx context.Context, deadline *t } -func (api *CommonAPI) CreatePREnforcement(ctx context.Context, orgName, repoName string) error { +func (api *CommonAPI) CreatePREnforcement(ctx context.Context, orgName, repoName, branchName string) error { addition := models.RepositoryAddition{ FilePath: ".github/workflows/branchProtections.yml", RepoName: repoName, OwnerName: orgName, - DestinationBranch: "main", + DestinationBranch: branchName, Content: utils.TargetBranchProtectionAction(), CommitMessage: "Deadline enforcement GH action files", } diff --git a/backend/internal/handlers/classrooms/assignments/assignments.go b/backend/internal/handlers/classrooms/assignments/assignments.go index 861df0fa..8f05552c 100644 --- a/backend/internal/handlers/classrooms/assignments/assignments.go +++ b/backend/internal/handlers/classrooms/assignments/assignments.go @@ -154,7 +154,7 @@ func (s *AssignmentService) generateAssignmentToken() fiber.Handler { // Uses an assignment token to accept an assignment. func (s *AssignmentService) useAssignmentToken() fiber.Handler { - //@CamTODO-> Downgrade student access + //@KHO-239 return func(c *fiber.Ctx) error { token := c.Params("token") if token == "" { @@ -244,7 +244,7 @@ func (s *AssignmentService) useAssignmentToken() fiber.Handler { initialDelay *= 2 } - //@CamTODO: Get rid of happy path here with repo get fail + //KHO-239 err = client.CreateBranchRuleset(c.Context(), classroom.OrgName, forkName) if err != nil { return errs.CriticalGithubError() diff --git a/backend/internal/handlers/webhooks/webhooks.go b/backend/internal/handlers/webhooks/webhooks.go index 981a6f1e..b082fe9e 100644 --- a/backend/internal/handlers/webhooks/webhooks.go +++ b/backend/internal/handlers/webhooks/webhooks.go @@ -2,7 +2,6 @@ package webhooks import ( "errors" - "fmt" "strings" "github.com/CamPlume1/khoury-classroom/internal/errs" @@ -98,7 +97,7 @@ func (s *WebHookService) baseRepoInitialization(c *fiber.Ctx, pushEvent github.P //Create PR Enforcement Action - err = s.appClient.CreatePREnforcement(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name) + err = s.appClient.CreatePREnforcement(c.Context(), *pushEvent.Repo.Organization, *pushEvent.Repo.Name, "main") if err != nil { return err } @@ -178,7 +177,6 @@ func (s *WebHookService) updateWorkStateOnStudentCommit(c *fiber.Ctx, pushEvent // Store updated student work locally _, err = s.store.UpdateStudentWork(c.Context(), studentWork) if err != nil { - fmt.Println("Crud error 10") return errs.InternalServerError() }