-
Notifications
You must be signed in to change notification settings - Fork 23
Feat: Learning Record For Residents #1169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
5be9161
3bb5fcb
66c223f
c89ffa9
59a3841
90e1fbf
382214f
33a2c41
9c3f26e
552410b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| -- +goose Up | ||
| CREATE TABLE learning_record_entries ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gtonye noting that the table should be qualified with the schema name
also where appropriate please make the change.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gtonye this table has
Can we add these to stay consistent with the rest of our tables (e.g. Also please make sure that when saving or updating records the values are being set/saved correctly |
||
| id SERIAL PRIMARY KEY, | ||
| user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, | ||
| client_id TEXT NOT NULL, | ||
| is_draft BOOLEAN NOT NULL DEFAULT FALSE, | ||
| step_index INTEGER NOT NULL DEFAULT 0, | ||
| ui_phase TEXT NOT NULL DEFAULT 'survey', | ||
| editing_entry_id INTEGER REFERENCES learning_record_entries(id) ON DELETE SET NULL, | ||
| program_name TEXT NOT NULL DEFAULT '', | ||
| completion_date TEXT NOT NULL DEFAULT '', | ||
| confidence TEXT NOT NULL DEFAULT '', | ||
| summary TEXT NOT NULL DEFAULT '', | ||
| top_skills TEXT NOT NULL DEFAULT '[]', | ||
| barrier_to_completion TEXT NOT NULL DEFAULT '', | ||
| goal_connection TEXT NOT NULL DEFAULT '', | ||
| pride TEXT NOT NULL DEFAULT '', | ||
| standout_moment TEXT NOT NULL DEFAULT '', | ||
| advice_to_peer TEXT NOT NULL DEFAULT '', | ||
| challenge_toggle TEXT, | ||
| challenge_text TEXT NOT NULL DEFAULT '', | ||
| skill_tags_before TEXT NOT NULL DEFAULT '[]', | ||
| skill_tags_after TEXT NOT NULL DEFAULT '[]', | ||
| skill_reflection TEXT NOT NULL DEFAULT '', | ||
| growth_reflection TEXT NOT NULL DEFAULT '', | ||
| support_selections TEXT NOT NULL DEFAULT '[]', | ||
| next_step_selections TEXT NOT NULL DEFAULT '[]', | ||
| created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), | ||
| updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_learning_record_entries_user_id | ||
| ON learning_record_entries(user_id); | ||
|
|
||
| CREATE UNIQUE INDEX IF NOT EXISTS idx_learning_record_entries_client_id | ||
| ON learning_record_entries(user_id, client_id); | ||
|
|
||
| -- +goose Down | ||
| DROP TABLE IF EXISTS learning_record_entries; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| -- +goose Up | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gtonye Not sure why we need this extra migration file, we usually try to use just one migration file per PR. Sometimes we run into a transaction issue where 2 is required. For consistency within our codebase can this bit of code be put into 00072 file. |
||
| -- +goose NO TRANSACTION | ||
| ALTER TYPE feature ADD VALUE IF NOT EXISTS 'learning_record'; | ||
|
|
||
| INSERT INTO public.feature_flags (name, enabled) | ||
| VALUES ('learning_record', FALSE) | ||
| ON CONFLICT (name) DO NOTHING; | ||
|
|
||
| -- +goose Down | ||
| DELETE FROM public.feature_flags WHERE name = 'learning_record'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Split Line 10 violates LT14 and can fail SQL lint gates. Proposed fix-DELETE FROM public.feature_flags WHERE name = 'learning_record';
+DELETE FROM public.feature_flags
+WHERE name = 'learning_record';🧰 Tools🪛 SQLFluff (4.2.1)[error] 10-10: The 'WHERE' keyword should always start a new line. (LT14) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,88 @@ | ||||||||||||||
| package database | ||||||||||||||
|
|
||||||||||||||
| import ( | ||||||||||||||
| "UnlockEdv2/src/models" | ||||||||||||||
| "errors" | ||||||||||||||
|
|
||||||||||||||
| "gorm.io/gorm" | ||||||||||||||
| "gorm.io/gorm/clause" | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| func (db *DB) GetLearningRecordEntries(userID uint) ([]models.LearningRecordEntry, error) { | ||||||||||||||
| entries := make([]models.LearningRecordEntry, 0) | ||||||||||||||
| if err := db.Where("user_id = ? AND is_draft = false", userID). | ||||||||||||||
| Order("created_at DESC"). | ||||||||||||||
| Find(&entries).Error; err != nil { | ||||||||||||||
| return nil, newGetRecordsDBError(err, "learning_record_entries") | ||||||||||||||
| } | ||||||||||||||
| return entries, nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (db *DB) CreateLearningRecordEntry(entry *models.LearningRecordEntry) error { | ||||||||||||||
| if err := db.Create(entry).Error; err != nil { | ||||||||||||||
| return newCreateDBError(err, "learning_record_entries") | ||||||||||||||
| } | ||||||||||||||
| return nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (db *DB) UpdateLearningRecordEntry(entry *models.LearningRecordEntry) error { | ||||||||||||||
| result := db.Model(entry). | ||||||||||||||
| Where("id = ? AND user_id = ?", entry.ID, entry.UserID). | ||||||||||||||
| Updates(entry) | ||||||||||||||
|
Comment on lines
+29
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing match checks allow false-success updates/deletes. Line 29-Line 31 (and similarly Line 39-Line 40, Line 83-Line 84) only check Suggested fix func (db *DB) UpdateLearningRecordEntry(entry *models.LearningRecordEntry) error {
- result := db.Model(entry).
+ result := db.Model(&models.LearningRecordEntry{}).
Where("id = ? AND user_id = ?", entry.ID, entry.UserID).
Updates(entry)
if result.Error != nil {
return newUpdateDBError(result.Error, "learning_record_entries")
}
+ if result.RowsAffected == 0 {
+ return newUpdateDBError(gorm.ErrRecordNotFound, "learning_record_entries")
+ }
return nil
}🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Struct Line 31 uses struct updates, so empty strings/zero numbers/false values are skipped by GORM. This breaks legitimate “clear this field” edits in learning record entries. Suggested fix- result := db.Model(&models.LearningRecordEntry{}).
- Where("id = ? AND user_id = ?", entry.ID, entry.UserID).
- Updates(entry)
+ result := db.Model(&models.LearningRecordEntry{}).
+ Where("id = ? AND user_id = ?", entry.ID, entry.UserID).
+ Select("*").
+ Omit("id", "user_id", "created_at").
+ Updates(entry)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
| if result.Error != nil { | ||||||||||||||
| return newUpdateDBError(result.Error, "learning_record_entries") | ||||||||||||||
| } | ||||||||||||||
| return nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (db *DB) DeleteLearningRecordEntry(id, userID uint) error { | ||||||||||||||
| if err := db.Where("id = ? AND user_id = ?", id, userID). | ||||||||||||||
| Delete(&models.LearningRecordEntry{}).Error; err != nil { | ||||||||||||||
| return newDeleteDBError(err, "learning_record_entries") | ||||||||||||||
| } | ||||||||||||||
| return nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // GetLearningRecordDraft returns the most recently updated draft for the user, or nil. | ||||||||||||||
| func (db *DB) GetLearningRecordDraft(userID uint) (*models.LearningRecordEntry, error) { | ||||||||||||||
| var draft models.LearningRecordEntry | ||||||||||||||
| err := db.Where("user_id = ? AND is_draft = true", userID). | ||||||||||||||
| Order("updated_at DESC"). | ||||||||||||||
| First(&draft).Error | ||||||||||||||
| if err != nil { | ||||||||||||||
| if errors.Is(err, gorm.ErrRecordNotFound) { | ||||||||||||||
| return nil, nil | ||||||||||||||
| } | ||||||||||||||
| return nil, newGetRecordsDBError(err, "learning_record_entries") | ||||||||||||||
| } | ||||||||||||||
| return &draft, nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // UpsertLearningRecordDraft inserts or updates a draft row keyed on (user_id, client_id). | ||||||||||||||
| func (db *DB) UpsertLearningRecordDraft(draft *models.LearningRecordEntry) error { | ||||||||||||||
| draft.IsDraft = true | ||||||||||||||
| if err := db.Clauses(clause.OnConflict{ | ||||||||||||||
| Columns: []clause.Column{{Name: "user_id"}, {Name: "client_id"}}, | ||||||||||||||
| DoUpdates: clause.AssignmentColumns([]string{ | ||||||||||||||
| "is_draft", "step_index", "ui_phase", "editing_entry_id", | ||||||||||||||
| "program_name", "completion_date", "confidence", "summary", | ||||||||||||||
| "top_skills", "barrier_to_completion", "goal_connection", | ||||||||||||||
| "pride", "standout_moment", "advice_to_peer", | ||||||||||||||
| "challenge_toggle", "challenge_text", | ||||||||||||||
| "skill_tags_before", "skill_tags_after", "skill_reflection", | ||||||||||||||
| "growth_reflection", "support_selections", "next_step_selections", | ||||||||||||||
| "updated_at", | ||||||||||||||
| }), | ||||||||||||||
| }).Create(draft).Error; err != nil { | ||||||||||||||
| return newCreateDBError(err, "learning_record_entries") | ||||||||||||||
| } | ||||||||||||||
| return nil | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| func (db *DB) DeleteLearningRecordDraft(userID uint, clientID string) error { | ||||||||||||||
| if err := db.Where("user_id = ? AND client_id = ? AND is_draft = true", userID, clientID). | ||||||||||||||
| Delete(&models.LearningRecordEntry{}).Error; err != nil { | ||||||||||||||
| return newDeleteDBError(err, "learning_record_entries") | ||||||||||||||
| } | ||||||||||||||
| return nil | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| package handlers | ||
|
|
||
| import ( | ||
| "UnlockEdv2/src/models" | ||
| "encoding/json" | ||
| "net/http" | ||
| "strconv" | ||
| ) | ||
|
|
||
| func (srv *Server) registerLearningRecordRoutes() []routeDef { | ||
| axx := models.LearningRecordAccess | ||
| return []routeDef{ | ||
| featureRoute("GET /api/learning-record/entries", srv.handleIndexLearningRecordEntries, axx), | ||
| featureRoute("POST /api/learning-record/entries", srv.handleCreateLearningRecordEntry, axx), | ||
| featureRoute("PUT /api/learning-record/entries/{id}", srv.handleUpdateLearningRecordEntry, axx), | ||
| featureRoute("DELETE /api/learning-record/entries/{id}", srv.handleDeleteLearningRecordEntry, axx), | ||
| featureRoute("GET /api/learning-record/draft", srv.handleGetLearningRecordDraft, axx), | ||
| featureRoute("PUT /api/learning-record/draft", srv.handleUpsertLearningRecordDraft, axx), | ||
| featureRoute("DELETE /api/learning-record/draft", srv.handleDeleteLearningRecordDraft, axx), | ||
| } | ||
| } | ||
|
|
||
| func (srv *Server) handleIndexLearningRecordEntries(w http.ResponseWriter, r *http.Request, log sLog) error { | ||
| userID := r.Context().Value(ClaimsKey).(*Claims).UserID | ||
| entries, err := srv.Db.GetLearningRecordEntries(userID) | ||
| if err != nil { | ||
| log.add("user_id", userID) | ||
| return newDatabaseServiceError(err) | ||
| } | ||
| return writeJsonResponse(w, http.StatusOK, entries) | ||
| } | ||
|
|
||
| func (srv *Server) handleCreateLearningRecordEntry(w http.ResponseWriter, r *http.Request, log sLog) error { | ||
| var entry models.LearningRecordEntry | ||
| if err := json.NewDecoder(r.Body).Decode(&entry); err != nil { | ||
| return newJSONReqBodyServiceError(err) | ||
| } | ||
| entry.UserID = r.Context().Value(ClaimsKey).(*Claims).UserID | ||
| if err := srv.Db.CreateLearningRecordEntry(&entry); err != nil { | ||
|
Comment on lines
+33
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 33-Line 39 and Line 55-Line 57 let clients control Suggested fix func (srv *Server) handleCreateLearningRecordEntry(w http.ResponseWriter, r *http.Request, log sLog) error {
var entry models.LearningRecordEntry
if err := json.NewDecoder(r.Body).Decode(&entry); err != nil {
return newJSONReqBodyServiceError(err)
}
entry.UserID = r.Context().Value(ClaimsKey).(*Claims).UserID
+ entry.IsDraft = false func (srv *Server) handleUpdateLearningRecordEntry(w http.ResponseWriter, r *http.Request, log sLog) error {
@@
entry.ID = uint(id)
entry.UserID = r.Context().Value(ClaimsKey).(*Claims).UserID
+ entry.IsDraft = falseAlso applies to: 55-57 🤖 Prompt for AI Agents |
||
| log.add("user_id", entry.UserID) | ||
| return newDatabaseServiceError(err) | ||
| } | ||
| return writeJsonResponse(w, http.StatusCreated, entry) | ||
| } | ||
|
|
||
| func (srv *Server) handleUpdateLearningRecordEntry(w http.ResponseWriter, r *http.Request, log sLog) error { | ||
| id, err := strconv.Atoi(r.PathValue("id")) | ||
| if err != nil { | ||
| return newInvalidIdServiceError(err, "entry ID") | ||
| } | ||
|
Comment on lines
+47
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject non-positive path IDs before uint conversion. Line 47-Line 50 and Line 66-Line 69 accept negative integers, then cast to Suggested fix id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
return newInvalidIdServiceError(err, "entry ID")
}
+if id <= 0 {
+ return newBadRequestServiceError(nil, "entry ID must be a positive integer")
+}Also applies to: 66-69 🤖 Prompt for AI Agents |
||
| var entry models.LearningRecordEntry | ||
| if err := json.NewDecoder(r.Body).Decode(&entry); err != nil { | ||
| return newJSONReqBodyServiceError(err) | ||
| } | ||
| entry.ID = uint(id) | ||
| entry.UserID = r.Context().Value(ClaimsKey).(*Claims).UserID | ||
| if err := srv.Db.UpdateLearningRecordEntry(&entry); err != nil { | ||
| log.add("entry_id", id) | ||
| log.add("user_id", entry.UserID) | ||
| return newDatabaseServiceError(err) | ||
| } | ||
| return writeJsonResponse(w, http.StatusOK, entry) | ||
| } | ||
|
|
||
| func (srv *Server) handleDeleteLearningRecordEntry(w http.ResponseWriter, r *http.Request, log sLog) error { | ||
| id, err := strconv.Atoi(r.PathValue("id")) | ||
| if err != nil { | ||
| return newInvalidIdServiceError(err, "entry ID") | ||
| } | ||
| userID := r.Context().Value(ClaimsKey).(*Claims).UserID | ||
| if err := srv.Db.DeleteLearningRecordEntry(uint(id), userID); err != nil { | ||
| log.add("entry_id", id) | ||
| log.add("user_id", userID) | ||
| return newDatabaseServiceError(err) | ||
| } | ||
| return writeJsonResponse(w, http.StatusOK, "Entry deleted successfully") | ||
| } | ||
|
|
||
| func (srv *Server) handleGetLearningRecordDraft(w http.ResponseWriter, r *http.Request, log sLog) error { | ||
| userID := r.Context().Value(ClaimsKey).(*Claims).UserID | ||
| draft, err := srv.Db.GetLearningRecordDraft(userID) | ||
| if err != nil { | ||
| log.add("user_id", userID) | ||
| return newDatabaseServiceError(err) | ||
| } | ||
| return writeJsonResponse(w, http.StatusOK, draft) | ||
| } | ||
|
|
||
| func (srv *Server) handleUpsertLearningRecordDraft(w http.ResponseWriter, r *http.Request, log sLog) error { | ||
| var draft models.LearningRecordEntry | ||
| if err := json.NewDecoder(r.Body).Decode(&draft); err != nil { | ||
| return newJSONReqBodyServiceError(err) | ||
| } | ||
| draft.UserID = r.Context().Value(ClaimsKey).(*Claims).UserID | ||
| if err := srv.Db.UpsertLearningRecordDraft(&draft); err != nil { | ||
| log.add("user_id", draft.UserID) | ||
| log.add("client_id", draft.ClientID) | ||
| return newDatabaseServiceError(err) | ||
| } | ||
| return writeJsonResponse(w, http.StatusOK, draft) | ||
| } | ||
|
|
||
| func (srv *Server) handleDeleteLearningRecordDraft(w http.ResponseWriter, r *http.Request, log sLog) error { | ||
| userID := r.Context().Value(ClaimsKey).(*Claims).UserID | ||
| clientID := r.URL.Query().Get("client_id") | ||
| if clientID == "" { | ||
| return newBadRequestServiceError(nil, "client_id query parameter is required") | ||
| } | ||
| if err := srv.Db.DeleteLearningRecordDraft(userID, clientID); err != nil { | ||
| log.add("user_id", userID) | ||
| log.add("client_id", clientID) | ||
| return newDatabaseServiceError(err) | ||
| } | ||
| return writeJsonResponse(w, http.StatusOK, "Draft deleted successfully") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| package models | ||
|
|
||
| import ( | ||
| "database/sql/driver" | ||
| "encoding/json" | ||
| "time" | ||
| ) | ||
|
|
||
| // StringSlice persists a Go string slice as a JSON text column. | ||
| type StringSlice []string | ||
|
|
||
| func (s StringSlice) Value() (driver.Value, error) { | ||
| if s == nil { | ||
| return "[]", nil | ||
| } | ||
| b, err := json.Marshal(s) | ||
| return string(b), err | ||
| } | ||
|
|
||
| func (s *StringSlice) Scan(value any) error { | ||
| var raw string | ||
| switch v := value.(type) { | ||
| case string: | ||
| raw = v | ||
| case []byte: | ||
| raw = string(v) | ||
| default: | ||
| raw = "[]" | ||
| } | ||
| return json.Unmarshal([]byte(raw), s) | ||
| } | ||
|
Comment on lines
+20
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return an error for unexpected types in The 🛡️ Proposed fix to return an error for unexpected types func (s *StringSlice) Scan(value any) error {
var raw string
switch v := value.(type) {
case string:
raw = v
case []byte:
raw = string(v)
+ case nil:
+ raw = "[]"
default:
- raw = "[]"
+ return fmt.Errorf("cannot scan type %T into StringSlice", value)
}
return json.Unmarshal([]byte(raw), s)
}🤖 Prompt for AI Agents |
||
|
|
||
| type LearningRecordEntry struct { | ||
| ID uint `gorm:"primaryKey" json:"id"` | ||
| UserID uint `gorm:"not null" json:"user_id"` | ||
| ClientID string `gorm:"not null" json:"client_id"` | ||
| IsDraft bool `gorm:"not null;default:false" json:"is_draft"` | ||
| StepIndex int ` json:"step_index"` | ||
| UiPhase string ` json:"ui_phase"` | ||
| EditingEntryID *uint ` json:"editing_entry_id"` | ||
| ProgramName string ` json:"program_name"` | ||
| CompletionDate string ` json:"completion_date"` | ||
| Confidence string ` json:"confidence"` | ||
| Summary string ` json:"summary"` | ||
| TopSkills StringSlice `gorm:"type:text" json:"top_skills"` | ||
| BarrierToCompletion string ` json:"barrier_to_completion"` | ||
| GoalConnection string ` json:"goal_connection"` | ||
| Pride string ` json:"pride"` | ||
| StandoutMoment string ` json:"standout_moment"` | ||
| AdviceToPeer string ` json:"advice_to_peer"` | ||
| ChallengeToggle *string ` json:"challenge_toggle"` | ||
| ChallengeText string ` json:"challenge_text"` | ||
| SkillTagsBefore StringSlice `gorm:"type:text" json:"skill_tags_before"` | ||
| SkillTagsAfter StringSlice `gorm:"type:text" json:"skill_tags_after"` | ||
| SkillReflection string ` json:"skill_reflection"` | ||
| GrowthReflection string ` json:"growth_reflection"` | ||
| SupportSelections StringSlice `gorm:"type:text" json:"support_selections"` | ||
| NextStepSelections StringSlice `gorm:"type:text" json:"next_step_selections"` | ||
| CreatedAt time.Time ` json:"created_at"` | ||
| UpdatedAt time.Time ` json:"updated_at"` | ||
| } | ||
|
|
||
| func (LearningRecordEntry) TableName() string { return "learning_record_entries" } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please revert this file back to its original form, this causes local testing to fail |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,10 @@ | |
| "date-fns": "^4.1.0", | ||
| "date-fns-tz": "^3.2.0", | ||
| "embla-carousel-react": "^8.6.0", | ||
| "html2canvas": "^1.4.1", | ||
| "html2pdf.js": "^0.14.0", | ||
| "input-otp": "^1.4.2", | ||
| "jspdf": "^4.2.1", | ||
|
Comment on lines
+49
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Search for html2pdf.js imports/usage in the codebase
rg -n --type=ts --type=tsx 'html2pdf' -g '!package.json' -g '!package-lock.json'Repository: UnlockedLabs/UnlockEdv2 Length of output: 95 🏁 Script executed: #!/bin/bash
set -euo pipefail
# 1) Search for any reference to html2pdf in source files (ts/tsx/js/jsx) under frontend/
rg -n --hidden \
--glob '!**/node_modules/**' \
--glob '!**/package-lock.json' \
--glob '!**/package.json' \
'html2pdf' frontend || true
# 2) Locate and inspect the referenced file
fd -a 'downloadLearningRecordPdf\.(ts|tsx|js|jsx)$' frontend/src frontend || true
echo "----"
FILE="$(fd -a 'downloadLearningRecordPdf\.(ts|tsx|js|jsx)$' frontend/src frontend | head -n 1 || true)"
if [ -n "$FILE" ]; then
echo "Inspecting: $FILE"
# show imports and whole file if not too large
wc -l "$FILE"
if [ "$(wc -l < "$FILE")" -le 250 ]; then
cat -n "$FILE"
else
sed -n '1,120p' "$FILE"
echo "..."
sed -n '120,240p' "$FILE"
fi
else
echo "downloadLearningRecordPdf.* not found"
fiRepository: UnlockedLabs/UnlockEdv2 Length of output: 8712 Remove
🤖 Prompt for AI Agents |
||
| "lucide-react": "^0.487.0", | ||
| "moment": "^2.30.1", | ||
| "next-themes": "^0.4.6", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was there a specific reason that the version number of the scripts are starting at 00072? The next version number should be 00069?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we can, please use the next available number for consistency