Skip to content

Commit

Permalink
feat: add system admin option to seed data for demo environment
Browse files Browse the repository at this point in the history
  • Loading branch information
PThorpe92 committed Dec 5, 2024
1 parent 902d279 commit a81ab48
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 41 deletions.
1 change: 0 additions & 1 deletion backend/src/database/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func (db *DB) GetDailyActivityByUserID(userID int, year int) ([]DailyActivity, e
if err := db.Where("user_id = ? AND created_at BETWEEN ? AND ?", userID, startDate, endDate).Find(&activities).Error; err != nil {
return nil, newGetRecordsDBError(err, "activities")
}

// Combine activities based on date
dailyActivities := make(map[time.Time]DailyActivity, 365)
for _, activity := range activities {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/database/libraries.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (db *DB) GetAllLibraries(page, perPage int, userId, facilityId uint, visibi

if search != "" {
search = "%" + strings.ToLower(search) + "%"
tx = tx.Where("LOWER(name) LIKE ?", search)
tx = tx.Where("LOWER(name) LIKE ? OR LOWER(description) LIKE ?", search, search)
}

if err := tx.Count(&total).Error; err != nil {
Expand Down
90 changes: 90 additions & 0 deletions backend/src/database/seed_demo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package database

import (
"UnlockEdv2/src/models"
"math/rand"
"time"

"github.com/google/uuid"
)

func (db *DB) RunOrResetDemoSeed(facilityId uint) error {
// seeding data for demo will only seed user activity/milestones/open-content activity for existing users
activity := models.Activity{}
if err := db.Model(&models.Activity{}).Where("external_id = 'SEEDED_ACTIVITY'").First(&activity).Error; err != nil {
return db.RunDemoSeed(facilityId)
}
if err := db.Raw("DELETE from activities where created_at > ?", activity.CreatedAt).Error; err != nil {
return newDeleteDBError(err, "activities")
}
if err := db.Raw("DELETE from open_content_activities where request_ts > ?", activity.CreatedAt).Error; err != nil {
return newDeleteDBError(err, "open_content_activities")
}
if err := db.Raw("DELETE from milestones where created_at > ?", activity.CreatedAt).Error; err != nil {
return newDeleteDBError(err, "milestones")
}
return db.RunDemoSeed(facilityId)
}

func (db *DB) RunDemoSeed(facilityId uint) error {
users := make([]models.User, 0, 20)
if err := db.Find(&users, "facility_id = ?", facilityId).Error; err != nil {
return newGetRecordsDBError(err, "users")
}
courses := make([]models.Course, 0, 10)
if err := db.Find(&courses).Error; err != nil {
return newGetRecordsDBError(err, "courses")
}
libraries := make([]models.Library, 0, 40)
if err := db.Model(&models.Library{}).Limit(40).Find(&libraries).Error; err != nil {
return newGetRecordsDBError(err, "libraries")
}
contentUrls := make([]models.OpenContentUrl, 0, 10)
if err := db.Model(&models.OpenContentUrl{}).Find(&contentUrls).Error; err != nil {
return newGetRecordsDBError(err, "open_content_urls")
}
if len(contentUrls) == 0 {
newContentUrls := []models.OpenContentUrl{{ContentURL: "/"}, {ContentURL: "/math"}, {ContentURL: "/science"}, {ContentURL: "/history"}}
if err := db.CreateInBatches(&newContentUrls, 4).Error; err != nil {
return newCreateDBError(err, "open_content_urls")
}
}
sixMonthsAgo := time.Now().AddDate(0, 0, -180)
for _, user := range users {
// seed user activity for two weeks for each user
for _, course := range courses {
courseTotalTime := int64(0)
for i := 0; i < 180; i++ {
randTime := rand.Int63n(500)
externalID := ""
if i == 0 {
externalID = "SEEDED_ACTIVITY"
} else {
externalID = uuid.NewString()
}
activity := models.Activity{CreatedAt: sixMonthsAgo.AddDate(0, 0, i).Add(time.Duration(i) * time.Minute), UserID: user.ID, CourseID: course.ID, ExternalID: externalID, Type: models.ContentInteraction, TotalTime: courseTotalTime, TimeDelta: randTime}
courseTotalTime += randTime
if err := db.Create(&activity).Error; err != nil {
continue
}
if i%5 == 0 {
milestone := models.Milestone{UserID: user.ID, CourseID: course.ID, ExternalID: uuid.NewString(), Type: models.AssignmentSubmission, IsCompleted: false}
if err := db.Create(&milestone).Error; err != nil {
continue
}
}
}
}
for i := 0; i < 180; i++ {
for _, library := range libraries {
if i%6 == 0 {
activity := models.OpenContentActivity{UserID: user.ID, RequestTS: sixMonthsAgo.AddDate(0, 0, i).Add((time.Duration(i)) * time.Minute), ContentID: library.ID, OpenContentProviderID: library.OpenContentProviderID, OpenContentUrlID: uint(rand.Intn(4) + 1), FacilityID: facilityId}
if err := db.Create(&activity).Error; err != nil {
continue
}
}
}
}
}
return nil
}
6 changes: 5 additions & 1 deletion backend/src/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,15 @@ func isAuthRoute(r *http.Request) bool {
return slices.Contains(paths, r.URL.Path)
}

func (srv *Server) UserIsAdmin(r *http.Request) bool {
func userIsAdmin(r *http.Request) bool {
claims := r.Context().Value(ClaimsKey).(*Claims)
return claims.isAdmin()
}

func userIsSystemAdmin(r *http.Request) bool {
return r.Context().Value(ClaimsKey).(*Claims).Role == models.SystemAdmin
}

func (srv *Server) canViewUserData(r *http.Request, id int) bool {
claims := r.Context().Value(ClaimsKey).(*Claims)
return slices.Contains(models.AdminRoles, claims.Role) || claims.UserID == uint(id)
Expand Down
13 changes: 13 additions & 0 deletions backend/src/handlers/feature_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
func (srv *Server) registerFeatureFlagRoutes() []routeDef {
return []routeDef{
{"PUT /api/auth/features/{feature}", srv.handleToggleFeatureFlag, true, models.Feature()},
{"POST /api/auth/demo-seed", srv.handleRunDemoSeed, true, models.Feature()},
}
}

Expand All @@ -31,3 +32,15 @@ func (srv *Server) handleToggleFeatureFlag(w http.ResponseWriter, r *http.Reques
srv.features = features
return writeJsonResponse(w, http.StatusOK, "feature toggled successfully")
}

func (srv *Server) handleRunDemoSeed(w http.ResponseWriter, r *http.Request, log sLog) error {
log.info("running seeder for demo environment")
if !userIsSystemAdmin(r) {
return newUnauthorizedServiceError()
}
err := srv.Db.RunOrResetDemoSeed(srv.getFacilityID(r))
if err != nil {
return newInternalServerServiceError(err, "unable to run demo seed")
}
return writeJsonResponse(w, http.StatusOK, "demo seed ran successfully")
}
6 changes: 3 additions & 3 deletions backend/src/handlers/libraries_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ func (srv *Server) handleIndexLibraries(w http.ResponseWriter, r *http.Request,
search := r.URL.Query().Get("search")
orderBy := r.URL.Query().Get("order_by")
showHidden := "visible"
if !srv.UserIsAdmin(r) && r.URL.Query().Get("visibility") == "hidden" {
if !userIsAdmin(r) && r.URL.Query().Get("visibility") == "hidden" {
return newUnauthorizedServiceError()
}
if srv.UserIsAdmin(r) {
if userIsAdmin(r) {
showHidden = r.URL.Query().Get("visibility")
}
claims := r.Context().Value(ClaimsKey).(*Claims)
Expand Down Expand Up @@ -97,7 +97,7 @@ func (srv *Server) handleToggleFavoriteLibrary(w http.ResponseWriter, r *http.Re
return newInternalServerServiceError(err, "error converting content id to int")
}
var facilityID *uint = nil
if srv.UserIsAdmin(r) {
if userIsAdmin(r) {
// an admin toggling this will save the facilityID as a 'featured' library for that facility
facilityID = &claims.FacilityID
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/handlers/milestones_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (srv *Server) handleIndexMilestones(w http.ResponseWriter, r *http.Request,
var milestones []database.MilestoneResponse
err := error(nil)
total := int64(0)
if !srv.UserIsAdmin(r) {
if !userIsAdmin(r) {
userId := srv.getUserID(r)
total, milestones, err = srv.Db.GetMilestonesForUser(page, perPage, userId)
if err != nil {
Expand Down
40 changes: 16 additions & 24 deletions backend/src/handlers/user_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,22 +100,15 @@ func (srv *Server) handleShowUser(w http.ResponseWriter, r *http.Request, log sL
return writeJsonResponse(w, http.StatusOK, user)
}

type NewUserResponse struct {
TempPassword string `json:"temp_password"`
User models.User `json:"user"`
}

type CreateUserRequest struct {
User models.User `json:"user"`
Providers []int `json:"provider_platforms"`
}

/**
* POST: /api/users
* TODO: transactional
**/
func (srv *Server) handleCreateUser(w http.ResponseWriter, r *http.Request, log sLog) error {
reqForm := CreateUserRequest{}
reqForm := struct {
User models.User `json:"user"`
Providers []int `json:"provider_platforms"`
}{}
err := json.NewDecoder(r.Body).Decode(&reqForm)
if err != nil {
return newJSONReqBodyServiceError(err)
Expand All @@ -132,26 +125,27 @@ func (srv *Server) handleCreateUser(w http.ResponseWriter, r *http.Request, log
if userNameExists {
return newBadRequestServiceError(err, "userexists")
}
reqForm.User.Username = stripNonAlphaChars(reqForm.User.Username)

err = srv.Db.CreateUser(&reqForm.User)
if err != nil {
return newDatabaseServiceError(err)
}
for _, providerID := range reqForm.Providers {
provider, err := srv.Db.GetProviderPlatformByID(providerID)
if err != nil {
log.add("providerID", providerID)
log.add("provider_id", providerID)
log.error("Error getting provider platform by id createProviderUserAccount")
return newDatabaseServiceError(err)
}
if err = srv.createAndRegisterProviderUserAccount(provider, &reqForm.User); err != nil {
log.add("providerID", providerID)
log.add("provider_id", providerID)
log.error("Error creating provider user account for provider: ", provider.Name)
}
}
tempPw := reqForm.User.CreateTempPassword()
response := NewUserResponse{
response := struct {
TempPassword string `json:"temp_password"`
User models.User `json:"user"`
}{
User: reqForm.User,
TempPassword: tempPw,
}
Expand Down Expand Up @@ -190,7 +184,7 @@ func (srv *Server) handleDeleteUser(w http.ResponseWriter, r *http.Request, log
}
if !srv.isTesting(r) {
if err := srv.deleteIdentityInKratos(&user.KratosID); err != nil {
log.add("KratosID", user.KratosID)
log.add("Kratos_id", user.KratosID)
return newInternalServerServiceError(err, "error deleting user in kratos")
}
}
Expand Down Expand Up @@ -237,19 +231,17 @@ func (srv *Server) handleUpdateUser(w http.ResponseWriter, r *http.Request, log
return writeJsonResponse(w, http.StatusOK, toUpdate)
}

type TempPasswordRequest struct {
UserID uint `json:"user_id"`
}

func (srv *Server) handleResetStudentPassword(w http.ResponseWriter, r *http.Request, log sLog) error {
temp := TempPasswordRequest{}
temp := struct {
UserID uint `json:"user_id"`
}{}
if err := json.NewDecoder(r.Body).Decode(&temp); err != nil {
return newJSONReqBodyServiceError(err)
}
defer r.Body.Close()
response := make(map[string]string)
user, err := srv.Db.GetUserByID(uint(temp.UserID))
log.add("temp.UserID", temp.UserID)
log.add("student.user_id", temp.UserID)
if err != nil {
return newDatabaseServiceError(err)
}
Expand All @@ -266,7 +258,7 @@ func (srv *Server) handleResetStudentPassword(w http.ResponseWriter, r *http.Req
claims.PasswordReset = true
if !srv.isTesting(r) {
if err := srv.handleUpdatePasswordKratos(claims, newPass, true); err != nil {
log.add("claims.UserID", claims.UserID)
log.add("claims.user_id", claims.UserID)
return newInternalServerServiceError(err, err.Error())
}
}
Expand Down
6 changes: 5 additions & 1 deletion backend/src/handlers/user_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ func TestCreateUser(t *testing.T) {
t.Fatalf("unable to get user from db, error is %v", err)
}
received := rr.Body.String()
resource := models.Resource[NewUserResponse]{}
type Resp struct {
TempPassword string `json:"temp_password"`
User models.User `json:"user"`
}
resource := models.Resource[Resp]{}
if err := json.Unmarshal([]byte(received), &resource); err != nil {
t.Errorf("failed to unmarshal resource, error is %v", err)
}
Expand Down
Loading

0 comments on commit a81ab48

Please sign in to comment.