-
Notifications
You must be signed in to change notification settings - Fork 5
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
Mailchimp #1332
Mailchimp #1332
Conversation
WalkthroughThis pull request introduces a comprehensive set of changes related to user marketing consent management across various components and services. A new React component, Changes
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 33
🧹 Outside diff range and nitpick comments (29)
services/headless-lms/models/src/marketing_consents.rs (4)
201-206
: Use consistent timestamps from application code instead ofnow()
Setting
synced_to_mailchimp_at = now()
directly in the SQL query can lead to slight timing inconsistencies, especially in concurrent environments. It can also make testing and debugging more challenging.Consider passing a consistent timestamp from the application code:
UPDATE user_marketing_consents - SET synced_to_mailchimp_at = now() + SET synced_to_mailchimp_at = $2 WHERE user_id = ANY($1::uuid[])And in your Rust code:
let current_timestamp = Utc::now(); sqlx::query!( " UPDATE user_marketing_consents SET synced_to_mailchimp_at = $2 WHERE user_id = ANY($1::uuid[]) ", &ids, current_timestamp ) .execute(conn) .await?;
216-216
: Rename parameterpool
toconn
for clarityThe parameter
pool
is of type&mut PgConnection
, which suggests an individual database connection rather than a connection pool. For consistency and clarity, consider renamingpool
toconn
, as in other functions.
252-252
: Align error handling by using consistent result typesThe function
update_bulk_user_consent
returnsanyhow::Result<()>
, while other functions in the module returnsqlx::Result<()>
orModelResult<()>
. For consistency across the codebase, consider using a uniform result type.For example, you could change the return type to
ModelResult<()>
:pub async fn update_bulk_user_consent( conn: &mut PgConnection, mailchimp_data: Vec<(String, bool, String, String)>, ) -> ModelResult<()> { // Function body }
135-135
: Simplify theCASE WHEN
statement in SQL queryThe
CASE WHEN
statement on line 135 can be simplified. Sincecmc.passed
is nullable, using it directly will suffice.Simplify the SQL as follows:
CASE WHEN cmc.passed IS NOT NULL THEN cmc.passed ELSE NULL END AS completed_courseCan be written as:
cmc.passed AS completed_courseservices/headless-lms/server/src/programs/mailchimp_syncer.rs (5)
97-99
: Consider handling the absence ofDATABASE_URL
more explicitlyCurrently, if the
DATABASE_URL
environment variable is not set, a default development database URL is used. This could lead to unintended connections in production environments. It would be safer to fail fast or log a warning if the environment variable is missing to prevent accidental misuse.
534-538
: Reusereqwest::Client
instead of creating a new one in each iterationCreating a new HTTP client inside the loop is inefficient and can lead to resource exhaustion. Consider reusing the existing
REQWEST_CLIENT
or instantiate the client outside of the loop.
602-605
: Use structured logging instead ofeprintln!
For consistency and better log management, replace
eprintln!
with the appropriate logging macros likeerror!
,warn!
, orinfo!
.Apply this diff:
- eprintln!( + error!(
482-508
: Handle unused variablefailed_user_ids
The variable
failed_user_ids
collects user IDs for which email updates failed but is not used afterward. Consider logging these failures or implementing retry logic to handle them.
225-225
: Specify appropriate field types when adding merge fieldsWhen adding merge fields to Mailchimp, ensure the field type matches the expected data. For example, fields like
MARKETING
could be better represented asdropdown
orradio
types instead oftext
. This enhances data integrity and user experience in Mailchimp.services/headless-lms/server/src/controllers/course_material/courses.rs (1)
940-941
: Fix endpoint formatting in the documentation commentThe endpoint in the documentation comment includes an extra hyphen before
fetch-user-marketing-consent
. Apply this diff to correct it:-GET `/api/v0/course-material/courses/:course_id/-fetch-user-marketing-consent` +GET `/api/v0/course-material/courses/:course_id/fetch-user-marketing-consent`services/headless-lms/models/.sqlx/query-a6659b3a5772ec77fc86b1a7e6abf2f884ee622559d13dfa0d47261cc9f8d4a2.json (1)
3-3
: Consider handling duplicate UUIDs in the input array.The current query might process the same UUID multiple times if it appears in the input array. Consider using DISTINCT to handle duplicates.
-UPDATE user_marketing_consents -SET synced_to_mailchimp_at = now() -WHERE user_id IN ( - SELECT UNNEST($1::uuid []) - ) +UPDATE user_marketing_consents +SET synced_to_mailchimp_at = now() +WHERE user_id IN ( + SELECT DISTINCT UNNEST($1::uuid[]) + )services/headless-lms/server/src/programs/mod.rs (1)
9-9
: Consider background service configuration and monitoring.Since this is a background service that will sync data with an external system (Mailchimp):
- Ensure proper retry mechanisms and error handling for network issues
- Consider implementing health checks and monitoring
- Plan for rate limiting and batch processing to handle large datasets
- Document the sync frequency and failure recovery procedures
services/headless-lms/models/.sqlx/query-11a20e21e4996d77f0cef1949b9efebe3aa4c2616f15ae2ef95e6940849a6fed.json (1)
1-18
: Consider adding audit trail columnsFor GDPR compliance and tracking purposes, it's important to maintain a history of consent changes.
Consider:
- Adding timestamp columns (
created_at
,updated_at
)- Adding version or revision tracking
- Implementing soft delete with
deleted_at
- Creating a separate audit table to track consent history
Would you like me to provide a detailed implementation for these suggestions?
services/headless-lms/models/.sqlx/query-7cb0af035cdafd0dadcd587670bde30eb4e094a84a06a884c432bfc841c7f8bc.json (1)
2-3
: Consider adding LIMIT clauseThe query could potentially return multiple rows for the same user_id and course_id combination. Consider adding a LIMIT clause if only one record is expected.
SELECT id, course_id, course_language_groups_id, user_id, user_mailchimp_id, consent, created_at, updated_at, deleted_at, synced_to_mailchimp_at FROM user_marketing_consents - WHERE user_id = $1 AND course_id = $2 + WHERE user_id = $1 AND course_id = $2 + LIMIT 1services/headless-lms/models/.sqlx/query-668b7cb6022b915f1838997679cc60864b00491bb34f1559377a36cee1a6485c.json (1)
3-3
: Consider adding an index for performance optimization.Since this query filters on
exam_id
and includes a JOIN betweencourses
andcourse_exams
, consider adding an index oncourse_exams(exam_id)
if not already present. This would improve query performance, especially as the number of courses and exams grows.services/headless-lms/models/.sqlx/query-f53c5545a9f632da9076ce08319f10b891d1b22fa2716e958a3de831f22217c8.json (1)
Line range hint
1-133
: Consider consent management architecture implications.Implementing marketing consent at the course level raises some architectural considerations:
- GDPR compliance: Ensure this structure allows for easy consent withdrawal and audit trails
- Consider if course-level granularity is appropriate vs. organization-level consent
- Think about consent expiration and renewal mechanisms
These aspects might need additional database fields or related tables in the future.
services/headless-lms/models/.sqlx/query-b7647f94557ccce49973abf5a9829a594929d26cdbdebe3ae65a75e63b25d537.json (2)
3-3
: Improve SQL query formatting for better readabilityThe SQL query could benefit from consistent indentation and alignment. Also, the new
ask_marketing_consent
column is not aligned with other columns.Consider reformatting like this:
- c.ask_marketing_consent\nFROM courses as c\n + c.ask_marketing_consent +FROM courses as c
Consider adding an index to improve query performance
Based on the analysis of the table structure and existing indexes, I recommend:
- The DISTINCT is necessary as the LEFT JOIN with course_instances can produce duplicate course records when a course has multiple instances.
- There is no existing index that would efficiently support the query's WHERE clause conditions on
starts_at
,ends_at
, andcourse_id
.- Adding the following index would improve query performance:
CREATE INDEX idx_course_instances_temporal ON course_instances(course_id, starts_at, ends_at) WHERE deleted_at IS NULL;This index will help the database efficiently filter course instances by their time range and course_id while respecting the soft delete pattern.
🔗 Analysis chain
Line range hint
3-133
: Consider query performance with additional columnThe query uses DISTINCT with a LEFT JOIN, which could impact performance on large datasets. While adding one boolean column won't significantly affect performance, consider:
- Whether DISTINCT is necessary (are there duplicate course IDs from the JOIN?)
- If an index on course_instances(course_id, starts_at, ends_at, deleted_at) would help
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Look for existing indexes that might help this query # Search for index definitions fd -e sql | xargs rg -l 'CREATE.*INDEX.*course_instances'Length of output: 254
Script:
#!/bin/bash # Let's check the content of these migration files to understand the existing indexes cat services/headless-lms/migrations/20230622140948_add_unique_nulls_not_distict_to_unique_indexes.down.sql services/headless-lms/migrations/20210706120250_restrict_course_instance_null_names.up.sql # Also, let's look for the table definition to understand the structure fd -e sql | xargs rg 'CREATE TABLE.*course_instances'Length of output: 5976
Script:
#!/bin/bash # Let's check the course_instances table definition cat services/headless-lms/migrations/20210609111555_add_user_exercise_states.up.sql # Also let's check if there are any other indexes on course_instances fd -e sql | xargs rg 'CREATE.*INDEX.*course_id.*starts_at.*ends_at'Length of output: 2666
services/headless-lms/models/.sqlx/query-b57d40a7daf9d2ae609f953f48f756c796ee19b751ffe4740d06600cdae0fd69.json (1)
3-3
: Consider query performance optimization.The LEFT JOIN with
course_module_completions
might impact performance for users with many completions. Consider:
- Adding a covering index for the join conditions
- Evaluating if the completion status is always needed
Alternative query structure if completion status isn't always needed:
SELECT umc.id, -- other fields... EXISTS ( SELECT 1 FROM course_module_completions WHERE user_id = umc.user_id AND course_id = umc.course_id AND passed = true ) AS completed_course FROM user_marketing_consents AS umc -- other joins...services/headless-lms/models/.sqlx/query-074ad35b8fa2588b0c36be3f18ec160f8bd276628e02678031d28e6545dfbb22.json (1)
2-3
: Consider adding indexes for performance optimizationThe query filters on
course_language_groups_id
and comparessynced_to_mailchimp_at
withupdated_at
. Consider adding a composite index to optimize these operations.CREATE INDEX idx_user_marketing_consents_sync ON user_marketing_consents(course_language_groups_id, synced_to_mailchimp_at, updated_at);services/headless-lms/models/.sqlx/query-ee5080bbfb823d38cfbd65043c874be0cd06b4310d4a5156fdb306054916c9e5.json (1)
Line range hint
1-149
: Consider adding database constraints for marketing consent.While the query structure is correct, consider whether additional database constraints should be added for the
ask_marketing_consent
field, such as:
- A default value for backward compatibility
- A trigger to handle consent revocation
- An index if you'll be querying by this field frequently
Would you like me to provide example SQL for these constraints?
services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql (2)
29-29
: Fix typo in comment documentation.There's a typo in the comment: "Wheter" should be "Whether"
-COMMENT ON COLUMN user_marketing_consents.consent IS 'Wheter the user has given a marketing consent for a specific course.'; +COMMENT ON COLUMN user_marketing_consents.consent IS 'Whether the user has given a marketing consent for a specific course.';
8-11
: Consider adding indexes for foreign key columns.The table has multiple foreign key columns that will likely be used in JOIN operations and WHERE clauses. Consider adding indexes to improve query performance.
CREATE INDEX idx_user_marketing_consents_course_id ON user_marketing_consents(course_id); CREATE INDEX idx_user_marketing_consents_course_language_groups_id ON user_marketing_consents(course_language_groups_id); CREATE INDEX idx_user_marketing_consents_user_id ON user_marketing_consents(user_id);services/course-material/src/components/modals/CourseSettingsModal.tsx (1)
Line range hint
1-244
: Consider splitting the component for better maintainabilityThe
CourseSettingsModal
component is handling multiple responsibilities:
- Course language selection
- Course instance selection
- Marketing consent management
Consider extracting these into separate components to improve maintainability and testing. This would also make it easier to add more features in the future.
Example structure:
// CourseSettingsModal.tsx const CourseSettingsModal = () => ( <Dialog> <CourseLanguageSection /> <CourseInstanceSection /> <MarketingConsentSection /> <LanguageChangeWarning /> </Dialog> )services/headless-lms/models/src/exams.rs (2)
Line range hint
392-394
: Fix incorrect JOIN conditions in get_exams_for_courseThe current JOIN conditions are incomplete and could lead to incorrect results:
- The
course_exams
JOIN is missing the connection to theexams
table- This could result in a cross product or missing results
Apply this fix to correct the JOIN conditions:
- JOIN course_exams ON course_id = $1 - JOIN courses ON courses.id = $1 + JOIN course_exams ON course_exams.exam_id = exams.id AND course_exams.course_id = $1 + JOIN courses ON courses.id = course_exams.course_id
101-101
: Add explicit type casting for ask_marketing_consentTo ensure type safety and proper NULL handling for the boolean field:
- ask_marketing_consent + COALESCE(ask_marketing_consent, false) as ask_marketing_consentservices/headless-lms/models/src/courses.rs (1)
Line range hint
1-979
: Consider adding documentation for the marketing consent fieldThe new field would benefit from documentation explaining its purpose and usage, especially in the struct definitions.
Add documentation comments to the relevant structs:
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[cfg_attr(feature = "ts_rs", derive(TS))] pub struct Course { // ... other fields ... + /// Indicates whether the course requires marketing consent from users. + /// When true, users will be prompted to provide marketing consent specific to this course. pub ask_marketing_consent: bool, }shared-module/packages/common/src/bindings.ts (1)
216-216
: Unrelated change: Addedhide_citations
to ChatbotConversationInfoThis change appears to be unrelated to the marketing consent feature. While the change itself is valid, consider splitting unrelated changes into separate PRs for better traceability.
services/headless-lms/server/src/programs/seed/seed_courses.rs (1)
99-99
: Consider extracting the default marketing consent value to a constant.The changes correctly add the
ask_marketing_consent
field to allNewCourse
struct instances. However, the default valuefalse
is duplicated across multiple locations.Consider extracting the default value to a constant at the module level for better maintainability:
+const DEFAULT_MARKETING_CONSENT: bool = false; // Then use it in all NewCourse instances: - ask_marketing_consent: false, + ask_marketing_consent: DEFAULT_MARKETING_CONSENT,Also applies to: 2034-2034, 2161-2161, 3041-3041, 4449-4449
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (45)
services/course-material/src/components/forms/SelectMarketingConsentForm.tsx
(1 hunks)services/course-material/src/components/modals/CourseSettingsModal.tsx
(3 hunks)services/course-material/src/services/backend.ts
(3 hunks)services/headless-lms/entrypoint/src/main.rs
(1 hunks)services/headless-lms/migrations/20241023104801_add-marketing-consent.down.sql
(1 hunks)services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
(1 hunks)services/headless-lms/models/.sqlx/query-074ad35b8fa2588b0c36be3f18ec160f8bd276628e02678031d28e6545dfbb22.json
(1 hunks)services/headless-lms/models/.sqlx/query-0a19c9653e258dd34bce3053b2c7cecf9c5427530798ed0225f4a64d6f3880ba.json
(1 hunks)services/headless-lms/models/.sqlx/query-11a20e21e4996d77f0cef1949b9efebe3aa4c2616f15ae2ef95e6940849a6fed.json
(1 hunks)services/headless-lms/models/.sqlx/query-32396d664f3f617f45c734993b62373ceff25cf391850e08a2697e0dd03f8d94.json
(3 hunks)services/headless-lms/models/.sqlx/query-393824929f6e6e064757af094951bc418d2da0f66429c32c48708cd3dccd0f3a.json
(3 hunks)services/headless-lms/models/.sqlx/query-401662d18cc190e1bab25142d7c3f021a4e66262cd6784eef8bc68a0c2727471.json
(3 hunks)services/headless-lms/models/.sqlx/query-668b7cb6022b915f1838997679cc60864b00491bb34f1559377a36cee1a6485c.json
(3 hunks)services/headless-lms/models/.sqlx/query-748c01cf60043988fba2a02a5d1ea5c3fd35a5adbd69b62e5dd5daa0855596b5.json
(3 hunks)services/headless-lms/models/.sqlx/query-7cb0af035cdafd0dadcd587670bde30eb4e094a84a06a884c432bfc841c7f8bc.json
(1 hunks)services/headless-lms/models/.sqlx/query-9fbc4e80f16e7cab3c8928ed195aa6b786df2e76edf819f9decfa7c441591925.json
(3 hunks)services/headless-lms/models/.sqlx/query-a6659b3a5772ec77fc86b1a7e6abf2f884ee622559d13dfa0d47261cc9f8d4a2.json
(1 hunks)services/headless-lms/models/.sqlx/query-a9f3390d8ad3debab9850325c54beba6bf46eaaec156732e307dcaa0ce08f600.json
(3 hunks)services/headless-lms/models/.sqlx/query-b57d40a7daf9d2ae609f953f48f756c796ee19b751ffe4740d06600cdae0fd69.json
(1 hunks)services/headless-lms/models/.sqlx/query-b5da68bc470f028a050fcca492eaf73c18e7f97137ea7f5f4ce1f1322e8c137c.json
(3 hunks)services/headless-lms/models/.sqlx/query-b7647f94557ccce49973abf5a9829a594929d26cdbdebe3ae65a75e63b25d537.json
(3 hunks)services/headless-lms/models/.sqlx/query-cdf31684a716948b9ed005acb59f8b556d48af088e3f489e627565a330b26ffb.json
(3 hunks)services/headless-lms/models/.sqlx/query-e1a28636054909bb7e9622667a32bdcb2a17e6df2bf5b046fb6ecc92640943f5.json
(3 hunks)services/headless-lms/models/.sqlx/query-ee5080bbfb823d38cfbd65043c874be0cd06b4310d4a5156fdb306054916c9e5.json
(4 hunks)services/headless-lms/models/.sqlx/query-f53c5545a9f632da9076ce08319f10b891d1b22fa2716e958a3de831f22217c8.json
(3 hunks)services/headless-lms/models/.sqlx/query-fa2a4f76355cf8f829e239ae278ab72efc11d1cf746c7081e5ca1e25eee9f7cd.json
(3 hunks)services/headless-lms/models/src/chapters.rs
(1 hunks)services/headless-lms/models/src/courses.rs
(18 hunks)services/headless-lms/models/src/exams.rs
(1 hunks)services/headless-lms/models/src/lib.rs
(1 hunks)services/headless-lms/models/src/library/copying.rs
(4 hunks)services/headless-lms/models/src/marketing_consents.rs
(1 hunks)services/headless-lms/models/src/test_helper.rs
(1 hunks)services/headless-lms/server/src/controllers/course_material/courses.rs
(3 hunks)services/headless-lms/server/src/programs/mailchimp_syncer.rs
(1 hunks)services/headless-lms/server/src/programs/mod.rs
(1 hunks)services/headless-lms/server/src/programs/seed/seed_courses.rs
(5 hunks)services/headless-lms/server/src/programs/seed/seed_organizations/uh_cs.rs
(1 hunks)services/headless-lms/server/src/programs/seed/seed_organizations/uh_mathstat.rs
(4 hunks)services/headless-lms/server/src/test_helper.rs
(1 hunks)services/headless-lms/server/src/ts_binding_generator.rs
(1 hunks)services/main-frontend/src/components/forms/NewCourseForm.tsx
(2 hunks)services/main-frontend/src/components/page-specific/manage/courses/id/index/UpdateCourseForm.tsx
(3 hunks)shared-module/packages/common/src/bindings.guard.ts
(6 hunks)shared-module/packages/common/src/bindings.ts
(5 hunks)
✅ Files skipped from review due to trivial changes (1)
- services/headless-lms/migrations/20241023104801_add-marketing-consent.down.sql
🔇 Additional comments (96)
services/headless-lms/server/src/programs/mailchimp_syncer.rs (1)
297-311
: Add error handling for fetching Mailchimp data
Currently, if fetching Mailchimp data fails, the program may panic or proceed with empty data. Ensure that errors are properly caught and handled to prevent crashes and ensure data integrity.
Run the following script to check for unhandled errors in fetch_mailchimp_data_in_chunks
:
services/headless-lms/server/src/controllers/course_material/courses.rs (5)
9-9
: Verify the dependency on UserMarketingConsent
Ensure that the imported UserMarketingConsent
struct from headless_lms_models::marketing_consents
is correctly defined and compatible with its usage in the new functions.
905-910
: Definition of UserMarketingConsentPayload
is appropriate
The struct UserMarketingConsentPayload
correctly encapsulates the required fields course_language_groups_id
and consent
. This complies with best practices for data transfer objects.
915-937
: Ensure proper error handling and response codes
Verify that the update_marketing_consent
function correctly handles potential errors from the database operation and returns appropriate HTTP status codes. This ensures that the client can handle responses effectively.
1045-1050
: Confirm new routes are correctly registered
Ensure that the new routes for update_marketing_consent
and fetch_user_marketing_consent
are properly added and do not conflict with existing routes. This is crucial for the API endpoints to function as expected.
955-957
:
Correct error handling in fetch_user_marketing_consent
function
The current implementation may not handle errors correctly. Using .await?.ok()
is incorrect because the ?
operator will propagate errors, and calling .ok()
afterward on ()
doesn't achieve the intended result. Remove the .ok()
to properly handle the Result
.
Apply this diff to fix the error handling:
-let result =
- models::marketing_consents::fetch_user_marketing_consent(&mut conn, *course_id, &user.id)
- .await
- .ok();
+let result = models::marketing_consents::fetch_user_marketing_consent(&mut conn, *course_id, &user.id)
+ .await?;
Likely invalid or redundant comment.
services/headless-lms/models/.sqlx/query-a6659b3a5772ec77fc86b1a7e6abf2f884ee622559d13dfa0d47261cc9f8d4a2.json (1)
1-12
: Verify database performance optimization.
To ensure optimal performance for this query, we should verify that appropriate indexes exist on the user_marketing_consents
table.
services/headless-lms/server/src/programs/mod.rs (1)
9-9
: LGTM! Verify integration points.
The addition of the mailchimp_syncer
module aligns with the PR's objective to implement marketing consent functionality.
Let's verify the integration points:
✅ Verification successful
Integration points verified successfully
The mailchimp_syncer
module is well-integrated with proper:
- Registration in the main entrypoint for program execution
- Integration with marketing consent database models and structures
- Implementation of background service with interval-based execution
- Comprehensive error handling and logging
- Database interactions for marketing consent data
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the mailchimp_syncer integration
# Check for the main entry point registration
rg -A 5 "mailchimp_syncer" services/headless-lms/entrypoint/src/main.rs
# Check for related marketing consent structures
rg -A 10 "UserMarketingConsent" services/headless-lms/models/src/marketing_consents.rs
# Check for database migration files related to marketing consent
fd -e sql | rg -l "marketing_consent"
Length of output: 2209
Script:
#!/bin/bash
# Let's verify the mailchimp syncer implementation and its integration
# Check the implementation of mailchimp_syncer
ast-grep --pattern 'fn main() {
$$$
}' services/headless-lms/server/src/programs/mailchimp_syncer.rs
# Check for any background service or task scheduling patterns
rg -A 5 "tokio::time|interval|sleep|spawn" services/headless-lms/server/src/programs/mailchimp_syncer.rs
# Check for error handling and logging
rg "(error|warn|info|debug|trace)!" services/headless-lms/server/src/programs/mailchimp_syncer.rs
# Check for database interactions
rg "sqlx::(query|query_as)" services/headless-lms/server/src/programs/mailchimp_syncer.rs
Length of output: 1426
services/headless-lms/models/.sqlx/query-11a20e21e4996d77f0cef1949b9efebe3aa4c2616f15ae2ef95e6940849a6fed.json (2)
12-14
: LGTM! Parameters are properly typed
The query uses strongly-typed parameters which prevents SQL injection vulnerabilities.
1-18
: Verify related database queries and schema
Let's ensure consistency across related queries and validate the table structure.
✅ Verification successful
Schema and queries are properly structured
The verification confirms that:
-
The table schema is properly defined with:
- Correct foreign key constraints to
courses
,course_language_groups
, andusers
tables - Appropriate uniqueness constraint using
NULLS NOT DISTINCT
for(user_id, course_language_groups_id)
- Proper timestamps and soft deletion support
- Correct foreign key constraints to
-
The related queries are consistent and well-structured:
- Insert/update query with proper conflict handling
- Select queries with appropriate joins to related tables
- Mailchimp synchronization tracking with
synced_to_mailchimp_at
The implementation follows the established patterns in the codebase for similar consent-related tables and uniqueness constraints.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Find related SQL queries and schema definitions
# Look for other queries related to user_marketing_consents
echo "=== Related Queries ==="
rg -l "user_marketing_consents" --type sql
# Look for schema definition
echo -e "\n=== Schema Definition ==="
fd -e sql | xargs rg -l "CREATE TABLE.*user_marketing_consents"
# Look for other consent-related queries
echo -e "\n=== Other Consent Queries ==="
rg -l "consent" --type sql
Length of output: 1523
Script:
#!/bin/bash
# Let's examine the schema definition and related migrations
# Check the marketing consent table schema
echo "=== Marketing Consent Table Schema ==="
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
# Check indices for the table
echo -e "\n=== Indices on user_marketing_consents ==="
rg "CREATE.*INDEX.*user_marketing_consents" services/headless-lms/migrations/*
# Check if there are any foreign key constraints
echo -e "\n=== Foreign Key Constraints ==="
rg "FOREIGN KEY.*user_marketing_consents" services/headless-lms/migrations/*
rg "REFERENCES.*user_marketing_consents" services/headless-lms/migrations/*
# Look for any other queries in .sqlx files related to this table
echo -e "\n=== Related SQLX Queries ==="
rg -l "user_marketing_consents" services/headless-lms/models/.sqlx/
Length of output: 91992
Script:
#!/bin/bash
# Let's check the content of related SQLX queries to understand the complete usage pattern
echo "=== Related SQLX Query Contents ==="
for file in services/headless-lms/models/.sqlx/query-b57d40a7daf9d2ae609f953f48f756c796ee19b751ffe4740d06600cdae0fd69.json \
services/headless-lms/models/.sqlx/query-7cb0af035cdafd0dadcd587670bde30eb4e094a84a06a884c432bfc841c7f8bc.json \
services/headless-lms/models/.sqlx/query-074ad35b8fa2588b0c36be3f18ec160f8bd276628e02678031d28e6545dfbb22.json \
services/headless-lms/models/.sqlx/query-a6659b3a5772ec77fc86b1a7e6abf2f884ee622559d13dfa0d47261cc9f8d4a2.json; do
echo -e "\nFile: $file"
cat "$file"
done
# Check if there are any other tables with similar consent-related uniqueness constraints
echo -e "\n=== Similar Uniqueness Constraints ==="
rg "CONSTRAINT.*uniqueness.*UNIQUE.*NULLS NOT DISTINCT" services/headless-lms/migrations/*
Length of output: 42643
services/headless-lms/models/.sqlx/query-0a19c9653e258dd34bce3053b2c7cecf9c5427530798ed0225f4a64d6f3880ba.json (1)
28-30
:
Review security implications of exposing access_token
The query exposes sensitive access_token
data. Consider if this token needs to be retrieved in all cases or if it can be fetched separately when needed.
Let's verify the token usage:
services/course-material/src/components/forms/SelectMarketingConsentForm.tsx (1)
1-54
: Verify security measures and enhance accessibility
Please ensure:
- Backend calls are protected against CSRF attacks
- The consent form meets accessibility guidelines
services/headless-lms/models/.sqlx/query-7cb0af035cdafd0dadcd587670bde30eb4e094a84a06a884c432bfc841c7f8bc.json (2)
57-60
: LGTM: Parameter and nullability configuration
The parameter types and nullability constraints are well-defined:
- Critical fields (id, course_id, user_id, consent) are correctly set as non-nullable
- Integration fields (user_mailchimp_id, synced_to_mailchimp_at) are appropriately nullable
- Parameters are properly typed as UUIDs
2-3
: Verify indexes for query performance
The query filters on user_id
and course_id
. Ensure there's an appropriate index to support this query pattern.
services/headless-lms/models/.sqlx/query-a9f3390d8ad3debab9850325c54beba6bf46eaaec156732e307dcaa0ce08f600.json (3)
133-133
: LGTM: Hash update is correct
The hash value has been properly updated to reflect the query changes.
3-3
: Verify the existence of ask_marketing_consent column
The query has been updated to include the new ask_marketing_consent
column. Let's verify its existence in the database schema.
✅ Verification successful
Column ask_marketing_consent
is properly defined with appropriate migration files
The migration files show that:
- Column is added with a non-null constraint and a default value of FALSE
- Documentation comment explains its purpose
- Proper up/down migrations exist for adding/removing the column
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the column exists in the courses table
# Expected: Find migration file that adds this column
# Look for migration files that mention the new column
rg -l "ask_marketing_consent" --type sql
# Look for the column definition in the schema
fd -e sql -x grep -l "courses" {} \; | xargs rg "ask_marketing_consent"
Length of output: 739
129-130
: Verify migration strategy for non-nullable constraint
The ask_marketing_consent
column is marked as non-nullable (false in nullable array). This requires:
- A default value for existing rows during migration
- Handling in the application layer for new course creation
Please ensure there's a migration strategy in place.
✅ Verification successful
Migration strategy for non-nullable constraint is properly handled
The migration strategy is correctly implemented:
- The
up.sql
migration adds the column withNOT NULL DEFAULT FALSE
, ensuring existing rows get a default value - The schema change is reversible with a corresponding
down.sql
migration - The column's purpose is well-documented with a COMMENT statement
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Look for migration handling
# Expected: Find migration file with default value handling
# Search for relevant migration files
fd -e sql -x grep -l "courses" {} \; | xargs rg -A 5 -B 5 "ask_marketing_consent"
# Look for course creation handling in Rust code
ast-grep --pattern 'struct NewCourse {
$$$
ask_marketing_consent: $_
$$$
}'
Length of output: 1681
services/headless-lms/models/.sqlx/query-cdf31684a716948b9ed005acb59f8b556d48af088e3f489e627565a330b26ffb.json (4)
100-104
: LGTM! Column definition is well-structured
The column definition correctly specifies:
- Ordinal position: 19
- Name: ask_marketing_consent
- Type: Bool (PostgreSQL boolean)
133-133
: LGTM! Hash update is correct
The hash value has been updated to reflect the changes in the query.
3-3
: Verify database schema for the new column
The query now includes ask_marketing_consent
column, but we need to ensure the database schema has been properly migrated.
✅ Verification successful
Database schema is properly configured for the new column
The migration files confirm that:
- The
ask_marketing_consent
column is added withNOT NULL DEFAULT FALSE
constraint - The column is properly documented with a descriptive comment
- Both up and down migrations exist for safe rollback
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if the database migration for ask_marketing_consent exists
# and verify its default value since it's non-nullable
# Look for migration files containing ask_marketing_consent
fd -e sql . | xargs rg -l "ask_marketing_consent"
# Look for the column definition to verify default value
rg -A 3 "ask_marketing_consent.*BOOLEAN"
Length of output: 734
129-130
: Verify handling of non-nullable constraint
The column is marked as non-nullable (false), which means:
- Existing rows must have a default value
- New course creation must always include this field
✅ Verification successful
Based on the gathered information, I can now provide the final response:
Default value is properly handled for ask_marketing_consent column
The migration adds the column with a default value of FALSE:
ALTER TABLE courses
ADD COLUMN ask_marketing_consent BOOLEAN NOT NULL DEFAULT FALSE;
This ensures:
- Existing rows automatically get FALSE value
- New courses will get FALSE if not explicitly set
- The non-null constraint is satisfied for both existing and new data
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check course creation code to ensure ask_marketing_consent is always set
# Look for course creation/insertion code
ast-grep --pattern 'INSERT INTO courses'
# Check if the field is required in course creation forms/APIs
rg -A 5 "type NewCourse"
Length of output: 69
Script:
#!/bin/bash
# Let's try a different approach to find SQL queries and schema definitions
# Check the SQL query file content
cat services/headless-lms/models/.sqlx/query-cdf31684a716948b9ed005acb59f8b556d48af088e3f489e627565a330b26ffb.json
# Look for SQL migration files that might contain the table definition
fd -e sql -e SQL
# Search for course-related structs and types
rg -g '*.rs' -A 5 'struct.*Course'
Length of output: 73907
Script:
#!/bin/bash
# Let's check the migration that added the ask_marketing_consent column and related code
# Find the marketing consent migration
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
# Look for code that handles course creation
rg -g '*.rs' -A 5 'pub async fn (create|insert).*course'
# Look for NewCourse struct usage
ast-grep --pattern 'NewCourse { $$$'
Length of output: 6229
Script:
#!/bin/bash
# Let's check the default value handling in the code
# Look for code that handles course creation with NewCourse
rg -g '*.rs' -B 10 -A 5 'impl.*NewCourse'
# Check if there are any tests related to course creation
rg -g '*test*.rs' -A 5 'new_course|create_course'
# Look for any code that sets ask_marketing_consent
rg -g '*.rs' 'ask_marketing_consent.*='
Length of output: 4638
Script:
#!/bin/bash
# Let's check the NewCourse struct definition and its usage
# Look for NewCourse struct definition
rg -g '*.rs' -B 5 -A 15 'struct NewCourse'
# Check for any GraphQL mutations or API endpoints that create courses
rg -g '*.rs' -A 10 'mutation.*create.*course|POST.*course'
# Look for any test data that creates courses
rg -g '*.rs' -A 10 'test.*create.*course|mock.*course'
Length of output: 69566
services/headless-lms/models/.sqlx/query-401662d18cc190e1bab25142d7c3f021a4e66262cd6784eef8bc68a0c2727471.json (4)
100-104
: LGTM! Column definition is well-structured
The ask_marketing_consent
column is correctly defined as a boolean type, which is appropriate for storing consent flags.
133-133
: Hash update is correct
The hash value has been automatically updated to reflect the query changes.
129-130
: Verify migration strategy for non-nullable ask_marketing_consent
The ask_marketing_consent
column is configured as non-nullable. Please ensure that the migration handles existing rows appropriately by setting a default value.
✅ Verification successful
Migration strategy for non-nullable ask_marketing_consent is properly handled
The migration appropriately sets a default value of FALSE
when adding the non-nullable ask_marketing_consent
column, ensuring existing rows will have a valid state:
ADD COLUMN ask_marketing_consent BOOLEAN NOT NULL DEFAULT FALSE;
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check migration files for ask_marketing_consent column addition
# Search for relevant migration files
fd -e sql | xargs rg -A 5 'ALTER TABLE.*courses.*ask_marketing_consent'
Length of output: 450
Script:
#!/bin/bash
# Check the up migration file for ask_marketing_consent column addition
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
Length of output: 4514
3-3
: Verify index existence for the slug
column
The query now filters by slug
instead of join_code
. To ensure optimal query performance, we should verify that an appropriate index exists on the slug
column.
✅ Verification successful
Index exists but may not be optimal for the query
The search reveals that there are unique indexes on the slug
column, but they are specifically for ensuring uniqueness when deleted_at IS NOT NULL
. This doesn't help our query which filters for deleted_at IS NULL
. Let's verify if there's a general index that would benefit this query.
Let's check for the actual table constraints to get a complete picture of the indexing situation.
Based on all the gathered evidence, I can now provide a final response:
Query performance is ensured with existing indexes
The codebase analysis reveals that the courses
table has proper indexing for the query:
- A unique index
courses_slug_key_when_not_deleted
exists forslug
column - The query's WHERE clause (
slug = $1 AND deleted_at IS NULL
) is properly supported by the table's constraints and indexes - The change from
join_code
toslug
is safe from a performance perspective
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for indexes on the slug column in the courses table
# Search for CREATE INDEX statements in migration files
fd -e sql | xargs rg -A 2 'CREATE.*INDEX.*courses.*slug'
# Alternative: Search for index definitions in schema files
fd schema -e sql | xargs rg -A 2 'CREATE.*INDEX.*courses.*slug'
Length of output: 1766
Script:
#!/bin/bash
# Search for any other index on courses table that might cover our query
fd -e sql | xargs rg -B 2 -A 2 'CREATE.*INDEX.*courses.*'
# Also check schema.sql which might have the complete index definitions
fd schema.sql | xargs cat
Length of output: 5389
Script:
#!/bin/bash
# Search for table creation and constraints for courses table
fd -e sql | xargs rg -B 2 -A 5 'CREATE TABLE courses|ALTER TABLE courses ADD CONSTRAINT'
# Also specifically look for unique constraints on slug
fd -e sql | xargs rg -B 2 -A 2 'ALTER TABLE courses ADD CONSTRAINT.*slug'
Length of output: 946
services/headless-lms/models/.sqlx/query-9fbc4e80f16e7cab3c8928ed195aa6b786df2e76edf819f9decfa7c441591925.json (4)
129-130
: LGTM! Nullable configuration is consistent
The nullable configuration correctly matches the column definition, marking ask_marketing_consent
as non-nullable (false).
133-133
: LGTM! Hash update is present
The query hash has been properly updated to reflect the changes.
100-104
: Verify database migration for non-nullable column
The ask_marketing_consent
column is configured as non-nullable (Bool). Ensure there's a corresponding migration that:
- Adds the column with a default value for existing rows
- Handles the transition from nullable to non-nullable if applicable
✅ Verification successful
Let me check the content of these migration files to verify how the column is handled.
Migration for non-nullable column is properly handled
The migration properly handles the ask_marketing_consent
column by:
- Adding it as a non-nullable BOOLEAN with a DEFAULT FALSE value, which ensures existing rows get a valid value
- Including proper cleanup in the down migration
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Look for related migrations
fd -e sql . | xargs grep -l "ask_marketing_consent"
Length of output: 207
Script:
#!/bin/bash
# Check the content of the migration files
echo "=== UP migration ==="
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
echo -e "\n=== DOWN migration ==="
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.down.sql
Length of output: 4851
3-3
: Verify the necessity of returning marketing consent data during deletion
The query returns ask_marketing_consent
when soft-deleting a course. Consider whether this information is actually needed in the deletion response, as it might be unnecessary overhead.
✅ Verification successful
Based on the search results, I can now generate a final response. The code shows that the ask_marketing_consent
field is part of the course data structure and is returned during course deletion in models/src/courses.rs
. The deletion flow is used in the main frontend controller (controllers/main_frontend/courses.rs
) where the complete course data is returned to the client, likely for displaying confirmation or maintaining consistency in the frontend state.
Returning marketing consent data during deletion is appropriate
The return of ask_marketing_consent
during course deletion is justified as:
- The deletion endpoint returns the complete course object to the frontend
- This maintains consistency with other course-related API responses
- The overhead is minimal as it's just one boolean field in an already-returned object
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if the returned marketing consent is used in deletion flows
rg -A 5 "DELETE.*courses.*ask_marketing_consent"
Length of output: 50
Script:
#!/bin/bash
# Let's search for the usage of this specific query file and the deletion flow
rg -l "9fbc4e80f16e7cab3c8928ed195aa6b786df2e76edf819f9decfa7c441591925"
# Also search for course deletion related code
ast-grep --pattern 'async fn delete_course'
Length of output: 118
Script:
#!/bin/bash
# Let's try a broader search for course deletion related code and the returned fields
rg -A 10 "delete.*course" services/headless-lms/
# Also search for the struct that might be using these returned fields
ast-grep --pattern 'struct Course {
$$$
}'
Length of output: 29209
services/headless-lms/models/.sqlx/query-fa2a4f76355cf8f829e239ae278ab72efc11d1cf746c7081e5ca1e25eee9f7cd.json (2)
129-130
: Review the nullability of join_code
The join_code
column is marked as nullable (true) but it's used as a filtering condition in the WHERE clause. This could potentially lead to unexpected behavior if NULL values are allowed. Consider:
- Making
join_code
non-nullable if it's a required field - Adding a NOT NULL check in the WHERE clause if it must remain nullable
Let's check the table definition:
✅ Verification successful
Let me check for any migrations that might have added the join_code
column since it's not in the initial table creation.
Let me check the migration that added the join_code
column to understand its constraints:
Let me check the actual SQL query that uses this join_code to understand the context better:
The nullability of join_code is intentional and safe
The current design is correct for the following reasons:
- The
join_code
column is intentionally nullable as shown in the migration that added it - The query is used to find courses by their join code, which is only relevant for courses that have
is_joinable_by_code_only
set to true - NULL values in
join_code
won't cause issues because:- The WHERE clause
join_code = $1
will only match non-NULL values that equal the input parameter - This aligns with the business logic where not all courses need a join code
- The WHERE clause
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check the courses table definition to understand join_code constraints
rg -A 10 "CREATE TABLE.*courses" --type sql
Length of output: 5378
Script:
#!/bin/bash
# Search for migrations that alter the courses table and add join_code
rg -B 2 -A 2 "ALTER TABLE.*courses.*join_code|ADD.*join_code.*courses" --type sql
Length of output: 439
Script:
#!/bin/bash
# Get the up migration that added join_code
cat services/headless-lms/migrations/20240930042528_add-join-code-uses.up.sql
Length of output: 1817
Script:
#!/bin/bash
# Find the SQL query that uses join_code in the WHERE clause
cat "services/headless-lms/models/.sqlx/query-fa2a4f76355cf8f829e239ae278ab72efc11d1cf746c7081e5ca1e25eee9f7cd.json"
Length of output: 3157
3-3
: LGTM! Verify the query usage in the codebase.
The SQL query changes look good. The addition of ask_marketing_consent
column and the switch to join_code
filtering aligns with the PR objective.
Let's verify the query usage:
✅ Verification successful
The SQL query changes look good - no issues found
The switch from slug
to join_code
filtering in the query is consistent with the codebase. The code shows:
- The
join_code
field is properly used in the courses table and related queries - There are dedicated endpoints and controllers for join code functionality
- The change aligns with the existing join code feature implementation
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if there are any remaining references to the old slug-based filtering
# that might need to be updated to use join_code instead.
rg -l "WHERE.*slug.*=.*courses" --type sql
Length of output: 44
Script:
#!/bin/bash
# Let's try a broader search for SQL queries filtering by slug in courses table
rg -A 5 "FROM courses.*WHERE.*slug" --type sql
# Also search for join_code usage to understand the context better
rg -A 5 "FROM courses.*WHERE.*join_code" --type sql
# Let's also check the actual SQL files in the repository
fd -e sql -x cat {}
Length of output: 144230
Script:
#!/bin/bash
# Let's search for SQL queries that use join_code in courses table
rg -A 3 "FROM courses.*join_code" --type json
# Also search for any references to slug-based filtering in sqlx files
rg -A 3 "FROM courses.*slug" --type json
# Let's also check if there are any other files using join_code
rg -l "join_code" --type rust
Length of output: 894
Script:
#!/bin/bash
# Let's check the actual implementation in courses.rs to understand the context
rg -A 10 "join_code" services/headless-lms/models/src/courses.rs
# Also check the SQL query usage in controllers
rg -A 10 "join_code" services/headless-lms/server/src/controllers/main_frontend/courses.rs
# And check if there are any tests related to this functionality
rg -A 10 "join_code" services/headless-lms/server/tests/study_registry_test.rs
Length of output: 7331
services/headless-lms/models/.sqlx/query-e1a28636054909bb7e9622667a32bdcb2a17e6df2bf5b046fb6ecc92640943f5.json (4)
100-104
: LGTM! Column definition is correctly structured
The new column definition follows the correct format and is properly typed as Bool with the correct ordinal position.
3-3
: Verify database migration for the new non-nullable column
The query now includes ask_marketing_consent
as a non-nullable column. Ensure that a migration exists to:
- Add the column with a default value
- Populate existing rows
- Set the column as NOT NULL
✅ Verification successful
Migration exists and properly handles the non-nullable column
The migration file 20241023104801_add-marketing-consent.up.sql
correctly:
- Adds the column with a default value:
ADD COLUMN ask_marketing_consent BOOLEAN NOT NULL DEFAULT FALSE
- Existing rows are handled by the DEFAULT FALSE constraint
- Column is set as NOT NULL from the start
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for related migration files
fd -e sql -e SQL . | xargs rg -i "ask_marketing_consent|alter.*courses"
Length of output: 8425
133-133
: Verify hash value consistency
The hash value has been updated. Ensure it matches the actual content of the query and its configuration.
✅ Verification successful
Let me try a different approach to verify the hash consistency.
Hash value matches the query content
The hash value in the filename and the "hash" field in the JSON file are identical (e1a28636054909bb7e9622667a32bdcb2a17e6df2bf5b046fb6ecc92640943f5), and they correspond to the SQL query and its configuration defined in the file. This is a valid SQLx query file with consistent hash values.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# List all query hashes in the directory for cross-reference
fd -e json . | xargs rg "hash.*:"
Length of output: 9637
Script:
#!/bin/bash
# Get the content of the query file and its hash
cd services/headless-lms/models/.sqlx/
cat query-e1a28636054909bb7e9622667a32bdcb2a17e6df2bf5b046fb6ecc92640943f5.json
Length of output: 3165
129-130
: Verify related insert/update queries
Since this column is non-nullable, ensure that all related insert and update queries for the courses table include this field.
✅ Verification successful
Let me gather more information about the insert/update queries by searching for SQL files and queries.
Let me search specifically for SQL queries related to course inserts and updates.
Let me check the content of these specific sqlx query files that contain INSERT/UPDATE operations for the courses table.
Based on the SQL queries found, I can now provide a final response:
All insert/update queries for the courses table include the ask_marketing_consent field
Looking at the SQL queries:
- The INSERT queries (ee5080b and cc2fceb) include ask_marketing_consent as a required field
- The main UPDATE query (f53c554) includes ask_marketing_consent in its SET clause
- The field is non-nullable (false in the nullable array) and has a default value of FALSE from the migration
The field is properly handled in all relevant queries, so there's no need for additional changes.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for related query files
fd -e json . | xargs rg -l "courses.*insert|courses.*update"
# Search for TypeScript interfaces/types
fd -e ts -e tsx . | xargs rg -A 5 "interface.*Course|type.*Course"
Length of output: 85586
Script:
#!/bin/bash
# Search for SQL files and queries related to courses table
fd -e sql . | xargs rg -l "courses"
# Search for .sqlx files and their content
fd -e sqlx . | xargs rg -A 5 "courses"
# Search for JSON query files related to courses
fd -e json . services/headless-lms/models/.sqlx/ | xargs cat
Length of output: 137666
Script:
#!/bin/bash
# Search for SQL files with insert/update queries for courses table
fd -e sql . services/headless-lms/migrations/ | xargs rg -l "INSERT INTO courses|UPDATE courses"
# Search for SQL files that create/modify the courses table
fd -e sql . services/headless-lms/migrations/ | xargs rg -A 5 "CREATE TABLE.*courses|ALTER TABLE.*courses"
# Search for query files specifically in the sqlx directory
fd -e json . services/headless-lms/models/.sqlx/ | xargs rg -l "INSERT INTO courses|UPDATE courses"
Length of output: 35907
Script:
#!/bin/bash
# Get content of the identified sqlx query files
cat services/headless-lms/models/.sqlx/query-f53c5545a9f632da9076ce08319f10b891d1b22fa2716e958a3de831f22217c8.json
cat services/headless-lms/models/.sqlx/query-f5433a51465291a5dfe643100be0591d5de7b1537e60288fc92ac534d3242f16.json
cat services/headless-lms/models/.sqlx/query-ee5080bbfb823d38cfbd65043c874be0cd06b4310d4a5156fdb306054916c9e5.json
cat services/headless-lms/models/.sqlx/query-cc2fceb2f75d704b551705f15ffcdae210fce2baf7b9d3612f77b6548a6df68c.json
cat services/headless-lms/models/.sqlx/query-9fbc4e80f16e7cab3c8928ed195aa6b786df2e76edf819f9decfa7c441591925.json
cat services/headless-lms/models/.sqlx/query-8202eb97a2b1f388a51758e7fcf8d69b2e68544fae551e3da0a41bc6e9a2de9c.json
Length of output: 12209
services/headless-lms/models/.sqlx/query-748c01cf60043988fba2a02a5d1ea5c3fd35a5adbd69b62e5dd5daa0855596b5.json (3)
129-130
: LGTM: Nullability configuration is correct
The nullability configuration properly marks ask_marketing_consent
as non-nullable (false), which aligns with the business requirement of always having a defined marketing consent state.
3-3
: Verify database migration for non-nullable column
The query includes the new ask_marketing_consent
column which is marked as non-nullable. Ensure that a proper migration exists that:
- Adds the column with a default value for existing rows
- Sets the NOT NULL constraint after populating existing rows
✅ Verification successful
Migration is properly implemented for the non-nullable column
The migration files show that:
- The
ask_marketing_consent
column is added withNOT NULL DEFAULT FALSE
, which safely handles existing rows - The migration includes proper up/down scripts with related tables for the marketing consent feature
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for migration files that add ask_marketing_consent column
fd -e sql -e SQL | xargs rg -l "ask_marketing_consent"
# Search for TypeScript/Rust files that might contain migration code
rg -t typescript -t rust "ask_marketing_consent.*migration"
Length of output: 307
Script:
#!/bin/bash
# Let's check the content of the migration files
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.down.sql
Length of output: 4739
100-104
: Consider adding an index for performance optimization
Since this query is filtering on course_language_group_id
and the new ask_marketing_consent
column might be used in WHERE clauses in other queries, consider adding an index if there are performance requirements.
services/headless-lms/models/.sqlx/query-32396d664f3f617f45c734993b62373ceff25cf391850e08a2697e0dd03f8d94.json (4)
133-133
: Hash update looks correct.
The hash has been properly updated to reflect the changes in the query structure.
129-130
: Verify default value for non-nullable column.
The ask_marketing_consent
column is configured as non-nullable. Ensure there's a default value defined in the migration to handle existing rows.
Let's check the migration:
✅ Verification successful
Default value is properly defined for non-nullable column
The migration file shows that the ask_marketing_consent
column is added with NOT NULL DEFAULT FALSE
, which properly handles existing rows by setting them to false
by default.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Look for the migration that adds this column
fd -e sql | xargs rg -l "ask_marketing_consent.*BOOLEAN"
Length of output: 135
Script:
#!/bin/bash
# Let's check the content of the migration file
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
Length of output: 4514
3-3
: Consider adding marketing consent validation in the WHERE clause.
The query fetches courses for a user but doesn't validate marketing consent status. Consider adding a condition to filter courses based on consent status if required by business logic.
Let's verify if marketing consent validation is needed:
100-104
: Consider adding an index for performance optimization.
Since ask_marketing_consent
is a boolean column that might be frequently used in WHERE clauses or joins, consider adding an index to optimize query performance.
Let's check if an index already exists:
services/headless-lms/models/.sqlx/query-668b7cb6022b915f1838997679cc60864b00491bb34f1559377a36cee1a6485c.json (2)
3-3
: LGTM! Query structure and new column addition look good.
The SQL query is well-structured with proper JOIN conditions and WHERE clause filters. The ask_marketing_consent
column is correctly added to the SELECT statement.
129-130
: Verify the non-nullable constraint implementation.
The ask_marketing_consent
column is configured as non-nullable (false
in nullable array). This design decision requires careful consideration:
- Existing rows in the courses table need a default value
- New course creation must handle this required field
✅ Verification successful
Based on the shell script results, I can now generate the final response since I have found the relevant migration that adds the ask_marketing_consent
column:
The non-nullable constraint implementation is properly handled
The migration file 20241023104801_add-marketing-consent.up.sql
shows that the column is added with a NOT NULL
constraint and a DEFAULT FALSE
value, which ensures:
- Existing rows will automatically get the default value of
false
- New course creation will use
false
if no value is specified
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for related migration files and default values
# Look for migration files that might handle the new column
fd -e sql . | xargs rg -l "ask_marketing_consent"
# Check for default value in table creation or modification
rg -A 5 "CREATE TABLE.*courses|ALTER TABLE.*courses"
Length of output: 35027
services/headless-lms/models/.sqlx/query-f53c5545a9f632da9076ce08319f10b891d1b22fa2716e958a3de831f22217c8.json (4)
100-104
: LGTM! Column metadata is well-structured.
The metadata for ask_marketing_consent
is correctly defined with appropriate type and ordinal position.
108-108
: LGTM! Parameters configuration is accurate.
The parameters array correctly includes all types including the new Bool type for ask_marketing_consent.
129-130
: Verify migration strategy for non-nullable marketing consent.
Since ask_marketing_consent
is set as non-nullable, ensure that:
- A database migration exists to add this column with a default value
- Existing records are handled appropriately
3-3
: Verify parameter ordering in the query.
The query structure looks good with proper parameterization. However, let's verify that the parameter ordering matches the application code to prevent runtime errors.
✅ Verification successful
Parameter ordering in the query is correct and matches the struct definition
Looking at the CourseUpdate
struct definition and the SQL query, the parameter ordering in the query matches exactly with the struct fields:
- name ($1)
- description ($2)
- is_draft ($3)
- is_test_mode ($4)
- can_add_chatbot ($5)
- is_unlisted ($6)
- is_joinable_by_code_only ($7)
- ask_marketing_consent ($8)
- id ($9)
The parameters are properly ordered and type-safe through SQLx's compile-time verification system.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify parameter ordering in related Rust code
# Look for the query usage to ensure parameters are passed in correct order
# Search for query usage in Rust files
rg -t rust "UPDATE courses.*SET name.*description.*is_draft.*is_test_mode.*can_add_chatbot.*is_unlisted.*is_joinable_by_code_only.*ask_marketing_consent" -A 5
Length of output: 160
Script:
#!/bin/bash
# Let's try a different approach to find the query usage by searching for the query hash
# The hash is from the filename: f53c5545a9f632da9076ce08319f10b891d1b22fa2716e958a3de831f22217c8
# Search for query hash in Rust files
rg -t rust "f53c5545a9f632da9076ce08319f10b891d1b22fa2716e958a3de831f22217c8" -B 5 -A 10
# Also search for the update_course function which likely uses this query
rg -t rust "update_course.*" -B 5 -A 10
Length of output: 13201
Script:
#!/bin/bash
# Let's look at the CourseUpdate struct to verify parameter ordering
ast-grep --pattern 'struct CourseUpdate {
$$$
}'
# Also let's look at the actual update query implementation
ast-grep --pattern 'sqlx::query_as!(
$$$
UPDATE courses
$$$
)'
Length of output: 123
Script:
#!/bin/bash
# Let's try a different approach using ripgrep to find the struct and query
# Search for CourseUpdate struct definition
rg -U "struct CourseUpdate \{[\s\S]*?\}" -A 5
# Search for the actual query implementation in update_course function
rg -U "pub async fn update_course.*?sqlx::query_as!\(.*?UPDATE courses.*?\)" -A 20 --multiline
Length of output: 1190
services/headless-lms/models/.sqlx/query-b7647f94557ccce49973abf5a9829a594929d26cdbdebe3ae65a75e63b25d537.json (3)
129-130
: LGTM! Nullability configuration is correct
The nullability array has been properly updated to mark ask_marketing_consent
as non-nullable (false), which matches the column description.
133-133
: LGTM! Hash has been updated
The query hash has been correctly updated to reflect the changes in the query and metadata.
100-104
: Verify the non-nullable constraint for ask_marketing_consent
The column description indicates that ask_marketing_consent
is a boolean field. Since it's marked as non-nullable in the nullability array, ensure that:
- The database table has a DEFAULT value for this column
- All existing rows have been migrated with appropriate values
✅ Verification successful
Column ask_marketing_consent
is properly constrained with a default value
The migration file shows that the column is added with NOT NULL DEFAULT FALSE
constraint, which ensures both existing and new rows will always have a valid boolean value.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the column constraints and default value
# Check if the column has a DEFAULT constraint
ast-grep --pattern 'ALTER TABLE courses ADD COLUMN ask_marketing_consent $$$'
# Check for any migration files that handle existing rows
fd -e sql -e go | xargs rg -l 'ask_marketing_consent'
Length of output: 532
services/headless-lms/models/.sqlx/query-b57d40a7daf9d2ae609f953f48f756c796ee19b751ffe4740d06600cdae0fd69.json (2)
3-3
: Review data privacy implications.
The query retrieves sensitive PII (email, first_name, last_name). Ensure that:
- This data is only used for the intended marketing consent synchronization
- Appropriate access controls are in place
- Data is properly encrypted in transit
✅ Verification successful
Let me gather more information about the data handling and security configurations.
Based on the code analysis, I can now provide a final response about the data privacy implications.
Data handling appears to be properly secured with appropriate controls
The query is part of a legitimate marketing consent synchronization system with the following security measures in place:
-
Data access is controlled:
- Data is only accessed through authenticated endpoints
- Marketing consent is explicitly tracked per user and course
- Syncing is handled by a dedicated mailchimp_syncer service
-
Data transmission security:
- Mailchimp API communication uses HTTPS (api.mailchimp.com)
- API calls use proper authentication with access tokens
-
Data minimization:
- PII is only synced after explicit user consent (tracked in user_marketing_consents table)
- Syncing is selective and only includes necessary fields
- Data is only updated when changes occur (tracked via synced_to_mailchimp_at)
-
Proper audit trail:
- All consent changes are timestamped (created_at, updated_at)
- Sync status is tracked (synced_to_mailchimp_at)
- Soft deletion is supported (deleted_at)
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for SSL configuration in database connection
rg -A 5 'ssl_mode|sslmode'
# Check for data encryption patterns
rg -A 5 'encrypt|cipher'
Length of output: 7772
Script:
#!/bin/bash
# Check for database configuration and connection settings
rg -A 10 'DATABASE_URL|db_url|postgres://' --type rust
# Check for mailchimp related code to understand data handling
rg -A 10 'mailchimp|marketing_consent' --type rust
# Look for environment configuration files
fd -e toml -e env -e yaml -x cat {}
Length of output: 115972
90-107
: Verify nullability configuration.
Some nullability configurations need verification:
completed_course
is marked asnull
(line 106) but has a type_info of "Bool"user_mailchimp_id
is nullable but might be required for Mailchimp sync
✅ Verification successful
Nullability configuration is correct
After reviewing the SQL query and table definition:
- The
completed_course
nullability is correctly set tonull
as it's derived from a LEFT JOIN withcourse_module_completions
and wrapped in a CASE statement that can return NULL. user_mailchimp_id
is correctly marked as nullable in both the table definition and query metadata, which is appropriate since it's populated after syncing with Mailchimp (as indicated by thesynced_to_mailchimp_at
column).
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for related table definitions
ast-grep --pattern 'CREATE TABLE user_marketing_consents'
Length of output: 59
Script:
#!/bin/bash
# Search for SQL table definitions using ripgrep
rg -i "create table.*user_marketing" -A 10
# Also search for any migrations related to this table
fd -e sql | xargs rg "user_marketing"
# Let's also check the actual query this JSON file corresponds to
cat services/headless-lms/models/.sqlx/query-b57d40a7daf9d2ae609f953f48f756c796ee19b751ffe4740d06600cdae0fd69.sql
Length of output: 4335
Script:
#!/bin/bash
# Let's check the content of the JSON file to see the full query metadata
cat services/headless-lms/models/.sqlx/query-b57d40a7daf9d2ae609f953f48f756c796ee19b751ffe4740d06600cdae0fd69.json
# Also search for any Rust code that might be using this query
rg "b57d40a7daf9d2ae609f953f48f756c796ee19b751ffe4740d06600cdae0fd69" -A 5 -B 5
Length of output: 3204
services/headless-lms/models/.sqlx/query-074ad35b8fa2588b0c36be3f18ec160f8bd276628e02678031d28e6545dfbb22.json (1)
2-3
: Verify security measures for PII data handling
This query retrieves sensitive user data (email, first/last name) for Mailchimp integration. Ensure proper security measures are in place.
✅ Verification successful
Let me gather more specific information about the data handling in this context.
Let me check one more aspect regarding the security configuration and data transmission.
Based on the gathered information, I can now provide the final response:
PII data handling appears to be properly secured
The implementation shows several security measures in place:
- HTTPS/TLS is used for Mailchimp API communication
- Sensitive configuration is handled through environment variables
- Database connections use secure configurations
- Data synchronization with Mailchimp is tracked and versioned
- Access tokens and secrets are properly managed through environment variables
The query is part of a well-structured system that handles PII data securely through:
- Controlled database access
- Proper authentication mechanisms
- Secure API communication
- Version tracking of synced data
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for encryption/security measures in related files
# Look for security-related configurations
echo "Checking for security configurations..."
rg -l "encryption|secure|sensitive" --type yaml --type json
# Check for proper error handling in related Rust files
echo "Checking for error handling in related files..."
ast-grep --pattern 'fn $_(user_marketing_consents|mailchimp)' -l rust
# Look for data protection patterns
echo "Checking for data protection patterns..."
rg -l "gdpr|data.protection|privacy" --type rust --type ts
Length of output: 1144
Script:
#!/bin/bash
# Check for Mailchimp-related implementations and data handling
echo "Checking Mailchimp integration implementations..."
rg -l "mailchimp" --type rust --type ts
# Look for database connection configurations
echo "Checking database connection configurations..."
rg -l "DATABASE_URL|ssl_mode|connection" --type yaml --type toml
# Check for specific user data handling patterns
echo "Looking for user data handling patterns..."
rg "UserMarketingConsents|user_marketing_consents" -A 5 --type rust
# Check for data transmission patterns
echo "Checking data transmission patterns..."
rg "send_to_mailchimp|sync.*mailchimp" -A 5 --type rust
Length of output: 19865
Script:
#!/bin/bash
# Check for TLS/HTTPS configuration and environment variables
echo "Checking for secure connection configurations..."
rg -l "https|tls|ssl" --type rust services/headless-lms/server/src/programs/mailchimp_syncer.rs
# Look for environment variable handling
echo "Checking environment variable handling..."
rg "env::" --type rust services/headless-lms/server/src/
# Check for any encryption or hashing implementations
echo "Checking for encryption patterns..."
rg "encrypt|hash|cipher" --type rust services/headless-lms/
Length of output: 16866
services/headless-lms/models/.sqlx/query-393824929f6e6e064757af094951bc418d2da0f66429c32c48708cd3dccd0f3a.json (4)
100-104
: LGTM! Column configuration is correct.
The ask_marketing_consent
column is properly defined as a boolean type with the correct ordinal position.
133-133
: LGTM! Hash update is consistent.
The hash value has been correctly updated to reflect the changes in the query configuration.
129-130
: Verify default value for non-nullable column.
The ask_marketing_consent
column is marked as non-nullable (false). This requires either:
- A default value in the database schema
- Explicit value provision in all INSERT statements
✅ Verification successful
Default value is properly set in the schema
The migration file shows that ask_marketing_consent
column has a default value of FALSE
set in the schema:
ADD COLUMN ask_marketing_consent BOOLEAN NOT NULL DEFAULT FALSE;
This ensures the non-nullable constraint is satisfied without requiring explicit values in INSERT statements.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for default value in schema and INSERT statements
# Search for table creation or modification
fd -e sql | xargs rg -A 5 'ask_marketing_consent.*DEFAULT'
# Search for INSERT statements to verify value provision
rg "INSERT\s+INTO\s+courses.*VALUES"
Length of output: 828
3-3
: Verify database indexes for optimal query performance.
The query involves multiple joins and subqueries. While the structure is correct, ensure proper indexing for optimal performance.
Consider adding the following indexes if not present:
roles(user_id, course_id)
for the first subqueryroles(user_id, course_instance_id)
for the second subquery
✅ Verification successful
Based on the search results, I can now provide a definitive response about the query performance and indexing:
Proper indexes exist for optimal query performance
The query is well-supported by existing indexes:
roles(user_id)
index exists for the first subquery (created in migration 20240905095603)course_instances
has proper indexing through foreign key constraints- Soft delete filtering (
deleted_at IS NULL
) is properly handled in index definitions
The query structure with subqueries and joins is appropriate for the access pattern, and the necessary indexes are in place to support efficient execution.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for indexes on joined tables and relevant columns
# Search for index definitions in migration files
fd -e sql | xargs rg -A 5 'CREATE\s+INDEX.*(?:courses|roles|course_instances)'
# Search for table definitions to verify column constraints
fd -e sql | xargs rg -A 10 'CREATE\s+TABLE.*courses.*ask_marketing_consent'
Length of output: 862
Script:
#!/bin/bash
# Let's search for more specific index definitions and table constraints
# Search for migrations related to ask_marketing_consent
fd -e sql | xargs rg -A 5 'ask_marketing_consent'
# Search for additional indexes on roles and course_instances
fd -e sql | xargs rg -A 3 'CREATE.*INDEX.*(roles|course_instances)'
# Search for foreign key constraints that might have indexes
fd -e sql | xargs rg -A 3 'FOREIGN KEY.*course_instance_id|course_id'
Length of output: 117639
services/headless-lms/models/.sqlx/query-b5da68bc470f028a050fcca492eaf73c18e7f97137ea7f5f4ce1f1322e8c137c.json (3)
100-104
: LGTM! Column metadata is correctly defined
The column metadata for ask_marketing_consent
is properly configured:
- Correct ordinal position (19)
- Appropriate type (Bool)
- Matches the SELECT query structure
129-130
: LGTM! Nullable constraints are properly set
The nullable array has been correctly updated to mark ask_marketing_consent
as non-nullable (false), which aligns with the business requirement of always having a defined marketing consent state.
3-3
: Verify database schema compatibility
The SQL query has been correctly updated to include the ask_marketing_consent
column. However, we should verify that this column exists in the database schema.
✅ Verification successful
Column schema matches the query requirements
The migration file 20241023104801_add-marketing-consent.up.sql
confirms that:
- The
ask_marketing_consent
column exists in thecourses
table - It's defined as
BOOLEAN NOT NULL DEFAULT FALSE
, which aligns with the query's metadata - The column has proper documentation via a
COMMENT
statement - The migration includes rollback support in the
.down.sql
file
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the existence of ask_marketing_consent column in courses table
# and its constraints
# Check if the column exists and its properties
rg -A 5 "ask_marketing_consent" --glob "*.sql" --glob "migrations/*"
Length of output: 1462
services/headless-lms/models/.sqlx/query-ee5080bbfb823d38cfbd65043c874be0cd06b4310d4a5156fdb306054916c9e5.json (4)
3-3
: LGTM! Query structure follows best practices.
The query is well-structured with proper parameterization ($1-$14) to prevent SQL injection, and the new ask_marketing_consent
column is correctly integrated into both INSERT and RETURNING clauses.
148-148
: LGTM! Hash update is present.
The query hash has been correctly updated to reflect the changes.
100-104
: Verify the column definition matches the database schema.
The column definition looks correct with proper type (Bool) and ordinal positioning (19).
✅ Verification successful
Column definition matches the database schema
The migration file 20241023104801_add-marketing-consent.up.sql
confirms that ask_marketing_consent
is added as a BOOLEAN
column to the courses
table, which matches the type Bool
in the query definition. The column is added with a NOT NULL
constraint and a default value of FALSE
.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the database schema matches the query definition
# Look for the table definition to confirm the column type and position
# Search for table definition files
fd -e sql | xargs rg -l 'CREATE TABLE.*courses'
# Search for migration files that might have added this column
fd -e sql | xargs rg -l 'ALTER TABLE.*courses.*ask_marketing_consent'
Length of output: 441
Script:
#!/bin/bash
# Let's check the content of the migration file that adds the column
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.down.sql
# And check the up migration as well (by changing .down. to .up.)
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
# Let's also check the initial table creation
cat services/headless-lms/migrations/20210216125447_setup_db.up.sql | grep -A 20 'CREATE TABLE.*courses'
Length of output: 5504
121-122
: Verify the parameter configuration is consistent.
The parameter and nullable configurations look correct:
- Parameter added as "Bool" type
- Nullable set to false, making it a required field
Also applies to: 144-145
✅ Verification successful
Parameter configuration is consistent across the codebase
The verification confirms that the ask_marketing_consent
parameter is consistently implemented:
- Database schema defines it as
BOOLEAN NOT NULL DEFAULT FALSE
- Rust structs and TypeScript bindings correctly use
bool
/boolean
type - Frontend forms and components handle it appropriately
- All test data and seed data consistently use boolean values
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the usage of ask_marketing_consent in the codebase
# to ensure the non-null constraint is handled properly
# Look for TypeScript/Rust struct definitions
ast-grep --pattern 'struct NewCourse {
$$$
ask_marketing_consent: $_
$$$
}'
# Look for form handling code
rg -A 5 "ask_marketing_consent.*required"
Length of output: 127
Script:
#!/bin/bash
# Let's try a broader search to understand the context and usage
# Search for any mention of ask_marketing_consent
rg "ask_marketing_consent"
# Look for the SQL query definition
fd -e json . --exec cat {} \; | jq -r 'select(.query | contains("ask_marketing_consent"))'
# Check the schema or migration files
fd -e sql . --exec cat {} \; | grep -A 5 -B 5 "ask_marketing_consent"
Length of output: 14236
services/headless-lms/entrypoint/src/main.rs (1)
71-74
: LGTM! The new program entry follows the established pattern.
The addition of the "mailchimp-syncer" program is well-structured and consistent with other program entries in the list.
Let's verify the existence of the referenced module and its main function:
✅ Verification successful
The shell script results show that the mailchimp_syncer
module exists at services/headless-lms/server/src/programs/mailchimp_syncer.rs
and contains a properly implemented main()
function with the expected signature pub async fn main() -> anyhow::Result<()>
. The module follows the same initialization pattern as other programs in the codebase (environment setup, database connection, etc.) and implements a continuous sync loop with proper error handling and logging.
The program entry is correctly implemented and the referenced module exists with proper implementation.
- The module exists at the expected location
- The main function has the correct signature and follows the codebase patterns
- The implementation includes proper initialization, error handling, and logging
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the existence of the mailchimp_syncer module and its main function
# Expected: Find the module file and confirm it has a main function
# Check if the module file exists
fd -t f "mailchimp_syncer.rs" services/headless-lms/
# Verify the main function exists in the module
ast-grep --pattern 'pub async fn main() -> anyhow::Result<()> { $$$ }'
Length of output: 51697
services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql (2)
1-4
: LGTM! Well-structured column addition with appropriate constraints.
The ask_marketing_consent
column is properly defined with a NOT NULL constraint and a sensible default value, accompanied by clear documentation.
20-21
: Verify trigger function dependency.
The migration uses trigger_set_timestamp()
function. Please verify that this function exists in the database before this migration runs.
Also applies to: 48-49
✅ Verification successful
Let me verify the content of the found migration file to ensure it actually creates the trigger function.
The trigger function dependency is properly satisfied
The trigger_set_timestamp()
function is created in an earlier migration file 20210216125447_setup_db.up.sql
. The function is created with CREATE OR REPLACE FUNCTION
and is already being used by multiple other tables in the database. The implementation matches the expected functionality for updating timestamps.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if the trigger function exists in previous migrations
fd -e sql -e SQL . | xargs grep -l "CREATE.*FUNCTION.*trigger_set_timestamp"
Length of output: 142
Script:
#!/bin/bash
# Check the content of the setup migration
cat services/headless-lms/migrations/20210216125447_setup_db.up.sql
Length of output: 3340
services/main-frontend/src/components/page-specific/manage/courses/id/index/UpdateCourseForm.tsx (3)
39-39
: LGTM: State management implementation is correct
The state initialization follows React best practices and properly derives its initial value from the course prop.
56-56
: LGTM: Update payload correctly includes marketing consent
The marketing consent field is properly included in the course update payload.
Line range hint 39-151
: Verify integration with marketing consent flow
The implementation adds the ability to toggle marketing consent requests for courses, but let's verify the complete user flow.
✅ Verification successful
Marketing consent implementation is properly integrated
The complete marketing consent flow is well-implemented across the codebase:
- Backend has proper database schema (
user_marketing_consents
table) and API endpoints for managing consent - Frontend has both admin controls (
UpdateCourseForm.tsx
) and user-facing components (SelectMarketingConsentForm.tsx
) - Integration with Mailchimp for consent synchronization is implemented
- Full flow includes:
- Course admin can enable/disable asking for marketing consent
- Users see consent form when enabled (
CourseSettingsModal.tsx
) - Consent data is stored and synced with external services
The implementation aligns perfectly with the PR objective of "User can give marketing consent to a specific course".
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for corresponding user-facing components that handle the actual consent
rg -t typescript -t tsx "marketing.*consent" --glob '!**/UpdateCourseForm.tsx'
# Check for related backend API endpoints
rg -t typescript "updateMarketingConsent|getMarketingConsent"
Length of output: 214
Script:
#!/bin/bash
# Search for marketing consent related code without file type restrictions
rg "marketing.*consent" --glob '!**/UpdateCourseForm.tsx'
# Search for API endpoints and types related to marketing consent
rg "marketing.*consent" -i --type-add 'api:*.{ts,tsx,js,jsx}'
# Look for GraphQL or API schema definitions
fd -e graphql -e gql -X cat
Length of output: 29847
services/course-material/src/components/modals/CourseSettingsModal.tsx (1)
8-12
: LGTM: Import statements are well-organized
The new imports for fetchCourseById
and SelectMarketingConstentForm
are properly organized and necessary for the marketing consent feature.
Also applies to: 15-15
services/headless-lms/models/src/lib.rs (1)
49-49
: LGTM! Verify the marketing_consents module implementation.
The addition of the marketing_consents
module follows the established pattern where each module corresponds to a database table. The module is correctly marked as public and placed in alphabetical order.
Let's verify the implementation of the marketing consents module:
✅ Verification successful
Marketing consents module implementation is complete and well-structured
The module implementation follows best practices with:
- Comprehensive data models (
UserMarketingConsent
,UserMarketingConsentWithDetails
,MarketingMailingListAccessToken
) - Complete set of database operations:
upsert_marketing_consent
for creating/updating consentsfetch_user_marketing_consent
for retrieving individual consentsfetch_all_unsynced_user_marketing_consents_by_course_language_groups_id
for batch processing- Mailchimp integration support with sync status tracking
- Bulk update capabilities via
update_bulk_user_consent
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the marketing_consents module implementation
# Expected: The module should define the necessary types and implement database operations
# Check if the module file exists and contains the expected types
fd -t f "marketing_consents.rs" services/headless-lms/models/src/ -x cat {}
# Look for database operations (insert, update, select) in the module
rg -A 5 "impl.*MarketingConsent" services/headless-lms/models/src/
Length of output: 9925
services/main-frontend/src/components/forms/NewCourseForm.tsx (1)
89-89
: Verify the property name and its purpose
The property name ask_marketing_consent
suggests it controls whether to ask for marketing consent, but it might be intended to store the actual consent. Consider renaming to better reflect its purpose:
- If it controls whether to show the consent prompt:
should_ask_marketing_consent
- If it stores the consent:
has_marketing_consent
Also applies to: 126-126
✅ Verification successful
Based on the search results, I can now generate the final response as I have sufficient context about the property's purpose from the SQL migration and its usage across the codebase:
Property name correctly reflects its purpose
The property name ask_marketing_consent
is correctly named as it indicates whether a course should ask users for marketing consent:
- SQL migration comment explicitly states: "Whether this course asks the user for marketing consent"
- The property controls the visibility of
SelectMarketingConstentForm
inCourseSettingsModal.tsx
- It's used in conjunction with a separate
user_marketing_consents
table that stores the actual consent data
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for property usage to understand its purpose
rg -A 5 "ask_marketing_consent"
Length of output: 24350
services/headless-lms/models/src/test_helper.rs (2)
179-179
: LGTM! Default value for marketing consent is appropriate.
The addition of ask_marketing_consent: false
as the default value for test data is a safe choice and aligns with the PR's objective of implementing marketing consent features.
179-179
: Verify consistency with NewCourse struct definition
Let's ensure this test helper stays in sync with the actual NewCourse
struct definition.
✅ Verification successful
Field ask_marketing_consent
is correctly defined in both the test helper and NewCourse struct
The verification confirms that the ask_marketing_consent
field in the test helper matches the definition in the NewCourse
struct. Both are defined as boolean fields.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that the NewCourse struct definition matches the test helper
# Expected: The NewCourse struct should contain the ask_marketing_consent field
# Search for the NewCourse struct definition
ast-grep --pattern 'struct NewCourse {
$$$
ask_marketing_consent: $_,
$$$
}' services/headless-lms/models/src/courses.rs
Length of output: 128
Script:
#!/bin/bash
# Let's first check if the file exists and see its content
cat services/headless-lms/models/src/courses.rs
# As a backup, let's also search for NewCourse struct definition in the entire codebase
rg "struct NewCourse" -A 20
Length of output: 39369
services/headless-lms/server/src/programs/seed/seed_organizations/uh_mathstat.rs (1)
Line range hint 1-365
: Document marketing consent behavior in README
Since this introduces a new feature for course-specific marketing consent, it would be valuable to document:
- The purpose and behavior of the
ask_marketing_consent
field - How it affects course enrollment
- Any default values or migration strategies
Consider adding this documentation to the relevant README file or creating a new documentation file specifically for marketing consent management.
Let's check if there's any existing documentation:
services/headless-lms/server/src/ts_binding_generator.rs (1)
173-174
: LGTM! Verify the model type exists.
The addition of UserMarketingConsent
type export is correctly placed and aligns with the PR's marketing consent feature.
Let's verify that the type exists in the models crate:
✅ Verification successful
Type exists and is correctly referenced
The UserMarketingConsent
type is properly defined in services/headless-lms/models/src/marketing_consents.rs
and is correctly imported in the server codebase. The TypeScript binding generator's reference to this type is valid.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that UserMarketingConsent type exists in the models crate
# Expected: Find the type definition in the marketing_consent module
# Search for the type definition
rg -p "struct UserMarketingConsent" services/headless-lms/server/
# Verify the module exists
fd -p "marketing_consent.rs" services/headless-lms/server/
Length of output: 243
Script:
#!/bin/bash
# Let's search for the actual UserMarketingConsent type and its module
rg -p "UserMarketingConsent" services/headless-lms/server/src/models/ -A 3
# Also check if it's defined in a different location
fd marketing_consent services/headless-lms/server/src/models/
# Check if it's imported from another crate
rg "use.*marketing_consent.*UserMarketingConsent" services/headless-lms/server/
Length of output: 708
Script:
#!/bin/bash
# The type seems to be in headless_lms_models crate, let's verify its location
fd -g "*.rs" -p headless_lms_models
# Check the marketing_consents module in headless_lms_models
rg -p "pub struct UserMarketingConsent" --type rust
Length of output: 455
services/headless-lms/server/src/test_helper.rs (1)
192-192
: LGTM! The new field is correctly added to the test data setup.
The addition of ask_marketing_consent: false
aligns with the PR objective of implementing marketing consent features and provides a reasonable default for test data.
services/headless-lms/models/src/exams.rs (1)
100-101
: Verify Course struct compatibility with new fields
The SQL query now includes join_code
and ask_marketing_consent
fields from the courses table. These fields must be properly defined in the Course
struct to avoid runtime errors.
Let's verify the Course struct definition:
✅ Verification successful
Course struct is compatible with the new fields
The Course struct in services/headless-lms/models/src/courses.rs
already includes both fields:
join_code: Option<String>
ask_marketing_consent: bool
The SQL query changes are properly aligned with the struct definition, ensuring type safety and preventing runtime errors.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if Course struct includes the new fields
# Expected: Course struct should have join_code and ask_marketing_consent fields
rg -A 20 "struct Course \{" services/headless-lms/models/src
Length of output: 1696
services/headless-lms/models/src/chapters.rs (1)
623-623
: LGTM! Verify consistent defaults across tests
The addition of ask_marketing_consent: false
is a necessary adaptation to support the new required field in NewCourse
. The default value is appropriate for this test case as it's not relevant to testing course module constraints.
Let's verify that this default value is consistent across other test cases:
✅ Verification successful
LGTM! Default value is consistently used
The verification shows that ask_marketing_consent: false
is consistently used as the default value in test helper macros across both services/headless-lms/models/src/test_helper.rs
and services/headless-lms/server/src/test_helper.rs
. These test helpers are used to create test data throughout the codebase, ensuring consistent behavior in test cases.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for consistent usage of ask_marketing_consent in test cases
# Expected: All test cases should set ask_marketing_consent to false when creating new courses
# Search for NewCourse struct initialization in test files
rg -U "NewCourse\s*\{(?s:.)*\}" --type rust --glob "**/test*"
Length of output: 26124
services/headless-lms/server/src/programs/seed/seed_organizations/uh_cs.rs (1)
500-500
: LGTM! Default opt-out for marketing consent aligns with privacy best practices.
The initialization of ask_marketing_consent
to false
is appropriate for seed data and follows good privacy practices by making marketing consent opt-in rather than opt-out.
Let's verify if this field is consistently set across all course creation calls:
✅ Verification successful
Based on the search results, I can now generate the final response since I have enough information to verify the concern. The search shows that ask_marketing_consent
is consistently set to false
across all NewCourse
struct initializations in the codebase, including:
- Test files (
study_registry_test.rs
) - Model implementations (
courses.rs
,chapters.rs
,copying.rs
) - Seed data files (
uh_cs.rs
,uh_mathstat.rs
,seed_courses.rs
)
LGTM! Marketing consent field is consistently set to false across all course initializations
The ask_marketing_consent
field is consistently set to false
in all course initializations throughout the codebase, following good privacy practices by making marketing consent opt-in by default.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if ask_marketing_consent is consistently set across all course creations
# Expected: All NewCourse struct initializations should include ask_marketing_consent field
# Search for NewCourse struct initializations
ast-grep --pattern 'NewCourse {
$$$
}'
# Search for create_new_course function calls
ast-grep --pattern 'create_new_course(
$$$
)'
Length of output: 30452
services/headless-lms/models/src/courses.rs (5)
51-51
: LGTM: Field addition to Course struct
The ask_marketing_consent
field is correctly added to the Course struct with appropriate visibility.
84-84
: LGTM: Field addition to NewCourse struct
The ask_marketing_consent
field is correctly added to the NewCourse struct, maintaining consistency with the Course struct.
582-582
: LGTM: Field addition to CourseUpdate struct
The ask_marketing_consent
field is correctly added to the CourseUpdate struct, enabling modification of the consent setting.
Line range hint 1-979
: Verify database migration for the new field
While the model changes look correct, ensure there's a corresponding database migration for adding the ask_marketing_consent
column.
✅ Verification successful
Based on the output, I can see that there are corresponding migration files for the ask_marketing_consent
column:
services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
adds the columnservices/headless-lms/migrations/20241023104801_add-marketing-consent.down.sql
removes the column
Let me verify the content of these migration files to ensure they properly handle the column addition:
Database migration for ask_marketing_consent
is properly implemented
The migration files show a complete implementation:
- Up migration adds the
ask_marketing_consent
boolean column with NOT NULL constraint and FALSE default - Creates supporting tables
user_marketing_consents
andmarketing_mailing_list_access_tokens
with proper foreign key constraints and triggers - Down migration properly removes the column and supporting tables
- All columns are well documented with comments
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Look for related database migration files
# Expected: Find migration file adding ask_marketing_consent column
fd -e sql -e rs "migration" | xargs rg "ask_marketing_consent"
Length of output: 4869
Script:
#!/bin/bash
# Check the content of the migration files
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.down.sql
Length of output: 4739
600-602
: Verify the SQL update statement parameter ordering
The SQL update statement and its parameters need careful verification to ensure the correct parameter ordering, as misalignment could lead to data corruption.
Also applies to: 631-631
✅ Verification successful
Parameter ordering in SQL update statement is correct and consistent
After examining the code in courses.rs
, the parameter ordering in the SQL update statement at lines 600-602 is correct and matches the order of fields being updated. The parameters $1 through $9 are properly aligned with their corresponding fields in both the update statement and the function parameters:
$1 -> name
$2 -> description
$3 -> is_draft
$4 -> is_test_mode
$5 -> can_add_chatbot
$6 -> is_unlisted
$7 -> is_joinable_by_code_only
$8 -> ask_marketing_consent
$9 -> id (WHERE clause)
The parameter ordering is consistent with the CourseUpdate
struct fields and the function parameters in update_course
. No issues were found with the parameter ordering.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify parameter ordering in SQL update statements
# Look for similar update statements in the codebase for consistency
rg -A 10 "UPDATE courses\nSET"
Length of output: 224
Script:
#!/bin/bash
# Description: Verify parameter ordering in SQL update statements
# Look for similar update statements in the codebase for consistency
# Using multiline mode and a more flexible pattern
rg -U "UPDATE courses.*SET" -A 10
# Also search for the specific function containing this update
ast-grep --pattern 'impl $_ {
$$$
async fn update_course($$$) {
$$$
}
$$$
}'
Length of output: 126
Script:
#!/bin/bash
# Let's try to find the file first and then examine its content
fd "courses.rs" --type f
# Then we'll look at the content of the specific file
cat services/headless-lms/models/src/courses.rs
Length of output: 24180
services/course-material/src/services/backend.ts (1)
52-52
: LGTM: Type imports and guards are properly organized.
The additions follow the established pattern of importing types and their corresponding guards.
Also applies to: 90-90
services/headless-lms/models/src/library/copying.rs (2)
1178-1178
: LGTM! Default value set appropriately.
The ask_marketing_consent
field is correctly initialized to false
by default in the test helper, which is a safe default value for new courses.
53-54
: LGTM! SQL query updated correctly for marketing consent.
The ask_marketing_consent
column has been properly added to both the INSERT and RETURNING clauses, maintaining consistency with the database schema changes.
Also applies to: 75-76, 90-91
✅ Verification successful
Let me search for the migration that adds the ask_marketing_consent
column.
Let me check the content of the migration file to verify the column addition.
SQL query changes are correctly aligned with the database schema
The ask_marketing_consent
column has been properly added to the courses table through a migration, and the SQL queries in the copying.rs file correctly include this new column. The migration file confirms:
- Column is added as
BOOLEAN NOT NULL DEFAULT FALSE
- Proper documentation is included via a COMMENT
- The column is used in the context of marketing consent features
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the column exists in the courses table
# Test: Check if ask_marketing_consent column exists in courses table
# Expect: Column should be present in the table definition
# Search for table definition
rg -A 20 "CREATE TABLE.*courses"
Length of output: 9685
Script:
#!/bin/bash
# Search for migration files that mention ask_marketing_consent
rg -l "ask_marketing_consent" services/headless-lms/migrations/
Length of output: 219
Script:
#!/bin/bash
# Check the content of the migration file
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
Length of output: 4514
shared-module/packages/common/src/bindings.ts (2)
478-478
: LGTM: Marketing consent flag consistently added to course interfaces
The ask_marketing_consent
boolean flag has been consistently added to all relevant course interfaces (Course
, CourseUpdate
, NewCourse
), maintaining type consistency across the codebase.
Also applies to: 508-508, 525-525
1156-1164
: LGTM: Well-structured UserMarketingConsent interface
The new interface includes all necessary fields for tracking user marketing consent:
- Proper ID and relationship fields (user_id, course_id)
- Timestamps for auditing (created_at, updated_at)
- Soft delete support (deleted_at)
- Boolean consent field
shared-module/packages/common/src/bindings.guard.ts (4)
598-599
: LGTM: Added hide_citations field check
The addition of the hide_citations
boolean field check in isChatbotConversationInfo
is implemented correctly.
1028-1029
: LGTM: Added join_code and ask_marketing_consent field checks
The addition of the optional join_code
string field and required ask_marketing_consent
boolean field checks in isCourse
are implemented correctly.
1077-1078
: LGTM: Added ask_marketing_consent field check
The addition of the ask_marketing_consent
boolean field check in isCourseUpdate
is implemented correctly.
2199-2211
: LGTM: New UserMarketingConsent type guard
The new isUserMarketingConsent
type guard function is implemented correctly with all required field checks:
- id (string)
- course_id (string)
- user_id (string)
- consent (boolean)
- created_at (string)
- updated_at (string)
- deleted_at (null | string)
let user_ids: Vec<Uuid> = mailchimp_data | ||
.iter() | ||
.filter_map(|(user_id, _, _, _)| Uuid::parse_str(user_id).ok()) | ||
.collect(); | ||
|
||
let consents: Vec<bool> = mailchimp_data | ||
.iter() | ||
.map(|(_, consent, _, _)| *consent) | ||
.collect(); | ||
|
||
let timestamps: Vec<DateTime<Utc>> = mailchimp_data | ||
.iter() | ||
.filter_map(|(_, _, ts, _)| { | ||
DateTime::parse_from_rfc3339(ts) | ||
.ok() | ||
.map(|dt| dt.with_timezone(&Utc)) // Convert to Utc | ||
}) | ||
.collect(); | ||
|
||
let course_language_groups_ids: Vec<Uuid> = mailchimp_data | ||
.iter() | ||
.filter_map(|(_, _, _, lang_id)| Uuid::parse_str(lang_id).ok()) | ||
.collect(); |
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.
Fix potential misalignment in vectors due to filter_map
usage
In update_bulk_user_consent
, the use of filter_map
for parsing user_ids
, timestamps
, and course_language_groups_ids
can result in vectors of differing lengths. This misalignment can cause incorrect associations between user IDs and their consents in the SQL query, leading to data integrity issues.
To address this, validate all fields together and only collect entries where all fields are successfully parsed:
let valid_data: Vec<(Uuid, bool, DateTime<Utc>, Uuid)> = mailchimp_data
.iter()
.filter_map(|(user_id_str, consent, ts_str, lang_id_str)| {
let user_id = Uuid::parse_str(user_id_str).ok()?;
let timestamp = DateTime::parse_from_rfc3339(ts_str).ok()?.with_timezone(&Utc);
let lang_id = Uuid::parse_str(lang_id_str).ok()?;
Some((user_id, *consent, timestamp, lang_id))
})
.collect();
let user_ids: Vec<Uuid> = valid_data.iter().map(|(user_id, _, _, _)| *user_id).collect();
let consents: Vec<bool> = valid_data.iter().map(|(_, consent, _, _)| *consent).collect();
let timestamps: Vec<DateTime<Utc>> = valid_data.iter().map(|(_, _, timestamp, _)| *timestamp).collect();
let course_language_groups_ids: Vec<Uuid> = valid_data.iter().map(|(_, _, _, lang_id)| *lang_id).collect();
// If the required field is missing, add it | ||
if let Err(e) = | ||
add_field_to_mailchimp(list_id, required_field, server_prefix, access_token).await | ||
{ | ||
warn!("Failed to add required field '{}': {}", required_field, e); | ||
} else { | ||
info!("Successfully added required field '{}'", required_field); | ||
} | ||
} else { |
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.
Handle errors when adding required fields to Mailchimp more robustly
If adding a required field fails, the code logs a warning and continues. This could lead to incomplete schema setup and errors during data synchronization. Consider implementing retries or aborting the synchronization process if critical fields cannot be added.
.bearer_auth(access_token) | ||
.send() |
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.
Inconsistent authentication method with Mailchimp API
The Mailchimp API expects the Authorization
header to include apikey
followed by the access token. Using .bearer_auth(access_token)
may not work as intended. Ensure consistent authentication across all API calls.
Apply this diff to fix the authentication method:
- .bearer_auth(access_token)
+ .header("Authorization", format!("apikey {}", access_token))
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
.bearer_auth(access_token) | |
.send() | |
.header("Authorization", format!("apikey {}", access_token)) | |
.send() |
"https://{}.api.mailchimp.com/3.0/lists/{}/members?offset={}&count={}&fields=members.merge_fields,members.status,members.last_changed?merge_fields[MARKETING]=disallowed", | ||
server_prefix, list_id, offset, chunk_size | ||
); |
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.
Fix malformed URL in fetch_mailchimp_data_in_chunks
The URL constructed for fetching Mailchimp data has improper placement of the ?
in the query parameters. This could lead to incorrect API calls or failures.
Apply this diff to correct the URL construction:
- "https://{}.api.mailchimp.com/3.0/lists/{}/members?offset={}&count={}&fields=members.merge_fields,members.status,members.last_changed?merge_fields[MARKETING]=disallowed",
+ "https://{}.api.mailchimp.com/3.0/lists/{}/members?offset={}&count={}&fields=members.merge_fields,members.status,members.last_changed&merge_fields[MARKETING]=disallowed",
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"https://{}.api.mailchimp.com/3.0/lists/{}/members?offset={}&count={}&fields=members.merge_fields,members.status,members.last_changed?merge_fields[MARKETING]=disallowed", | |
server_prefix, list_id, offset, chunk_size | |
); | |
"https://{}.api.mailchimp.com/3.0/lists/{}/members?offset={}&count={}&fields=members.merge_fields,members.status,members.last_changed&merge_fields[MARKETING]=disallowed", | |
server_prefix, list_id, offset, chunk_size | |
); |
eprintln!( | ||
"Error while processing chunk {}/{}: ", | ||
(total_records + BATCH_SIZE - 1) / BATCH_SIZE, | ||
e | ||
); |
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.
Fix formatting in error logging
The error message in eprintln!
does not correctly format the chunk number and the error. The variables are not properly referenced in the format string.
Apply this diff to correct the error message:
- eprintln!(
- "Error while processing chunk {}/{}: ",
- (total_records + BATCH_SIZE - 1) / BATCH_SIZE,
- e
- );
+ eprintln!(
+ "Error while processing chunk {}/{}: {}",
+ (total_records + BATCH_SIZE - 1) / BATCH_SIZE,
+ total_records / BATCH_SIZE,
+ e
+ );
Alternatively, use the error!
macro for consistent logging:
- eprintln!(
+ error!(
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
eprintln!( | |
"Error while processing chunk {}/{}: ", | |
(total_records + BATCH_SIZE - 1) / BATCH_SIZE, | |
e | |
); | |
eprintln!( | |
"Error while processing chunk {}/{}: {}", | |
(total_records + BATCH_SIZE - 1) / BATCH_SIZE, | |
total_records / BATCH_SIZE, | |
e | |
); |
@@ -189,6 +189,7 @@ macro_rules! insert_data { | |||
copy_user_permissions: false, | |||
is_joinable_by_code_only: false, | |||
join_code: None, | |||
ask_marketing_consent:false, |
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.
💡 Codebase verification
Based on the search results from our previous queries, we can see that while there is extensive implementation of marketing consent functionality in the codebase (models, controllers, etc.), there are no dedicated test cases covering the marketing consent scenarios. The absence of test coverage for both true and false cases of ask_marketing_consent
confirms the original review comment's concern.
Add test coverage for marketing consent scenarios
- No test cases found that verify the behavior when
ask_marketing_consent
is set to true or false - Implement tests for the following endpoints that handle marketing consent:
- POST
/api/v0/course-material/courses/:course_id/user-marketing-consent
- GET
/api/v0/course-material/courses/:course_id/fetch-user-marketing-consent
- POST
🔗 Analysis chain
Verify test coverage for marketing consent scenarios.
Consider adding test cases that specifically verify the behavior when ask_marketing_consent
is set to true. This will ensure both consent states are properly tested.
Let's check for existing test coverage:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Search for test cases related to marketing consent
# Look for test files and functions that might be testing marketing consent
rg -t rust "ask_marketing_consent.*true" services/headless-lms/
rg -t rust "#\[test\].*marketing.*consent" services/headless-lms/
Length of output: 133
Script:
#!/bin/bash
# Let's broaden our search to find all occurrences of marketing consent in test files
# and their surrounding context to better understand the test coverage
# Search for any mention of marketing consent in test files
rg -t rust "marketing.*consent" services/headless-lms/ -B 2 -A 2
# Search for all occurrences of ask_marketing_consent to see its usage
rg -t rust "ask_marketing_consent" services/headless-lms/ -B 2 -A 2
# Look for test helper functions that might be setting up test data
rg -t rust "fn.*test.*" services/headless-lms/server/src/test_helper.rs -B 2 -A 10
Length of output: 46251
Script:
#!/bin/bash
# Let's search for test files and test cases that might be testing marketing consent functionality
rg -t rust "test.*marketing" services/headless-lms/ -B 2 -A 5
# Look for actual test implementations involving marketing consent
ast-grep --pattern 'async fn test_$_($$$) {
$$$
ask_marketing_consent
$$$
}'
# Search for test files that might be using the marketing consent APIs
rg -t rust "upsert_marketing_consent|fetch_user_marketing_consent" services/headless-lms/server/src/tests/ -B 2 -A 5
Length of output: 347
@@ -959,6 +976,7 @@ mod test { | |||
copy_user_permissions: false, | |||
is_joinable_by_code_only: false, | |||
join_code: None, | |||
ask_marketing_consent: false, |
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.
🛠️ Refactor suggestion
Consider adding test cases for marketing consent
While the test module includes comprehensive language code validation tests, it lacks specific test cases for the new marketing consent functionality.
Consider adding the following test cases:
- Setting marketing consent during course creation
- Updating marketing consent
- Retrieving courses with different consent settings
#[tokio::test]
async fn test_marketing_consent_operations() {
insert_data!(:tx, user: _user, :org);
let course_language_group_id = course_language_groups::insert(
tx.as_mut(),
PKeyPolicy::Fixed(Uuid::parse_str("8e40c36c-835b-479c-8f07-863ad408f181").unwrap()),
)
.await
.unwrap();
// Test creation with marketing consent
let new_course = NewCourse {
// ... other fields ...
ask_marketing_consent: true,
};
let course_id = courses::insert(
tx.as_mut(),
PKeyPolicy::Fixed(Uuid::parse_str("95d8ab4d-073c-4794-b8c5-f683f0856356").unwrap()),
course_language_group_id,
&new_course,
)
.await
.unwrap();
// Test retrieval
let course = courses::get_course(tx.as_mut(), course_id).await.unwrap();
assert_eq!(course.ask_marketing_consent, true);
// Test update
let course_update = CourseUpdate {
// ... other fields ...
ask_marketing_consent: false,
};
let updated_course = courses::update_course(tx.as_mut(), course_id, course_update).await.unwrap();
assert_eq!(updated_course.ask_marketing_consent, false);
}
export const updateMarketingConsent = async ( | ||
courseId: string, | ||
courseLanguageGroupsId: string, | ||
consent: boolean, | ||
): Promise<string> => { | ||
const res = await courseMaterialClient.post( | ||
`courses/${courseId}/user-marketing-consent`, | ||
{ | ||
course_language_groups_id: courseLanguageGroupsId, | ||
consent, | ||
}, | ||
{ | ||
responseType: "json", | ||
}, | ||
) | ||
return validateResponse(res, isString) | ||
} |
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.
Fix the API endpoint path and consider improving the return type.
- The API endpoint path is missing a leading slash, which is inconsistent with other endpoints in this file.
- Consider using a more specific return type instead of
string
to better document the expected response.
Apply this diff to fix the path:
- `courses/${courseId}/user-marketing-consent`,
+ `/courses/${courseId}/user-marketing-consent`,
Consider creating a specific type for the response:
type MarketingConsentUpdateResponse = {
status: string;
// Add other relevant fields based on the actual API response
}
export const fetchUserMarketingConsent = async ( | ||
courseId: string, | ||
): Promise<UserMarketingConsent> => { | ||
const res = await courseMaterialClient.get(`courses/${courseId}/fetch-user-marketing-consent`) | ||
return validateResponse(res, isUserMarketingConsent) | ||
} |
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.
Fix the API endpoint path.
The API endpoint path is missing a leading slash, which is inconsistent with other endpoints in this file.
Apply this diff to fix the path:
- const res = await courseMaterialClient.get(`courses/${courseId}/fetch-user-marketing-consent`)
+ const res = await courseMaterialClient.get(`/courses/${courseId}/fetch-user-marketing-consent`)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const fetchUserMarketingConsent = async ( | |
courseId: string, | |
): Promise<UserMarketingConsent> => { | |
const res = await courseMaterialClient.get(`courses/${courseId}/fetch-user-marketing-consent`) | |
return validateResponse(res, isUserMarketingConsent) | |
} | |
export const fetchUserMarketingConsent = async ( | |
courseId: string, | |
): Promise<UserMarketingConsent> => { | |
const res = await courseMaterialClient.get(`/courses/${courseId}/fetch-user-marketing-consent`) | |
return validateResponse(res, isUserMarketingConsent) | |
} |
export const updateMarketingConsent = async ( | ||
courseId: string, | ||
courseLanguageGroupsId: string, | ||
consent: boolean, | ||
): Promise<string> => { | ||
const res = await courseMaterialClient.post( | ||
`courses/${courseId}/user-marketing-consent`, | ||
{ | ||
course_language_groups_id: courseLanguageGroupsId, | ||
consent, | ||
}, | ||
{ | ||
responseType: "json", | ||
}, | ||
) | ||
return validateResponse(res, isString) | ||
} | ||
|
||
export const fetchUserMarketingConsent = async ( | ||
courseId: string, | ||
): Promise<UserMarketingConsent> => { | ||
const res = await courseMaterialClient.get(`courses/${courseId}/fetch-user-marketing-consent`) | ||
return validateResponse(res, isUserMarketingConsent) | ||
} |
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.
💡 Codebase verification
API paths need leading slashes for consistency
The integration of marketing consent functions is properly implemented in the SelectMarketingConsentForm
component, but the API paths in backend.ts
should be updated with leading slashes for consistency:
courses/${courseId}/user-marketing-consent
→/courses/${courseId}/user-marketing-consent
courses/${courseId}/fetch-user-marketing-consent
→/courses/${courseId}/fetch-user-marketing-consent
🔗 Analysis chain
Verify the integration of marketing consent functions.
Let's verify how these functions are being used in the codebase to ensure proper integration.
Let me check the implementation in the form component to ensure proper integration.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for usages of the new marketing consent functions
echo "Searching for updateMarketingConsent usage..."
rg "updateMarketingConsent" --type ts
echo -e "\nSearching for fetchUserMarketingConsent usage..."
rg "fetchUserMarketingConsent" --type ts
# Search for related test files
echo -e "\nSearching for related test files..."
fd -e test.ts -e spec.ts marketing
Length of output: 1245
Script:
#!/bin/bash
# Get the implementation of SelectMarketingConsentForm
echo "Content of SelectMarketingConsentForm.tsx:"
cat services/course-material/src/components/forms/SelectMarketingConsentForm.tsx
# Check for any API path constants or configurations
echo -e "\nSearching for API path configurations:"
rg -g '!*.test.ts' -g '!*.spec.ts' "courses/.*/user-marketing-consent" --type ts
Length of output: 2045
}) => { | ||
const [marketingConsent, setMarketingConsent] = useState(false) | ||
|
||
useEffect(() => { |
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.
I think this you useQuery hook and after that useEffect if needed
@@ -71,6 +76,12 @@ const CourseSettingsModal: React.FC<React.PropsWithChildren<CourseSettingsModalP | |||
}) | |||
sortInstances() | |||
|
|||
const getCourse = useQuery({ | |||
queryKey: ["courses/${course_id}", selectedLangCourseId], |
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.
Make the first string to not look like a template string
CREATE TABLE user_marketing_consents ( | ||
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, | ||
course_id UUID NOT NULL REFERENCES courses(id), | ||
course_language_groups_id UUID NOT NULL REFERENCES course_language_groups(id), |
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.
course_language_group_id
let result = sqlx::query_as!( | ||
UserMarketingConsent, | ||
" | ||
SELECT id, |
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.
Try the select *
Ok(result) | ||
} | ||
|
||
// Used to update the synced_to_mailchimp_at to a list of users when they are successfully synced to mailchimp |
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.
Make it a documentation comment ///
@@ -138,6 +139,16 @@ const UpdateCourseForm: React.FC<React.PropsWithChildren<UpdateCourseFormProps>> | |||
checked={joinableByCodeOnlyStatus} | |||
/> | |||
</FieldContainer> | |||
<FieldContainer> | |||
<CheckBox | |||
// eslint-disable-next-line i18next/no-literal-string |
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.
Needs translating
if let Err(e) = | ||
add_field_to_mailchimp(list_id, required_field, server_prefix, access_token).await | ||
{ | ||
warn!("Failed to add required field '{}': {}", required_field, e); |
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.
Return an error
|
||
loop { | ||
let url = format!( | ||
"https://{}.api.mailchimp.com/3.0/lists/{}/members?offset={}&count={}&fields=members.merge_fields,members.status,members.last_changed?merge_fields[MARKETING]=disallowed", |
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.
Change it to filter based on mailchimp's unsubscribe status
|
||
const BATCH_SIZE: usize = 1000; // Adjust based on DB capabilities | ||
|
||
async fn process_mailchimp_data( |
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.
The naming could reflect that we're processing the unsubscribes
// | ||
|
||
/// Fetches data from Mailchimp in chunks. | ||
async fn fetch_mailchimp_data_in_chunks( |
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.
The naming could reflect that we're processing the unsubscribes
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.
Actionable comments posted: 8
🧹 Outside diff range and nitpick comments (9)
services/headless-lms/models/.sqlx/query-d2b6cb427cd241cddbfb9f4fabd743103c99859dc65a648774fdd8c6bb7d89b4.json (1)
3-3
: Add RETURNING clause for update verificationThe current query doesn't return any information about the affected rows. Consider adding a RETURNING clause to verify the success of the update operation.
- "query": "\nUPDATE user_marketing_consents\nSET user_mailchimp_id = updated_data.user_mailchimp_id\nFROM (\n SELECT UNNEST($1::uuid[]) AS user_id, UNNEST($2::text[]) AS user_mailchimp_id\n) AS updated_data\nWHERE user_marketing_consents.user_id = updated_data.user_id\n", + "query": "\nUPDATE user_marketing_consents\nSET user_mailchimp_id = updated_data.user_mailchimp_id\nFROM (\n SELECT UNNEST($1::uuid[]) AS user_id, UNNEST($2::text[]) AS user_mailchimp_id\n) AS updated_data\nWHERE user_marketing_consents.user_id = updated_data.user_id\nRETURNING user_id, user_mailchimp_id\n",services/headless-lms/models/.sqlx/query-5d95679ce5dfd7c1eafc3fb8d38892d0e9407051f27b20ea136276c634a4240b.json (1)
7-10
: Add array length validationThe query uses multiple UNNEST operations assuming all input arrays have the same length. This could lead to unexpected results if arrays have different lengths.
Consider adding a check in the application code before executing this query to ensure all input arrays have the same length. Example validation in TypeScript:
if (new Set([ userIds.length, lastUpdated.length, courseLanguageGroupIds.length, emailSubscriptions.length ]).size !== 1) { throw new Error('All input arrays must have the same length'); }services/headless-lms/models/.sqlx/query-0a881c84d211755c24c03586b0d5be217790808cb8751ae50d121321f0be9856.json (1)
2-3
: Consider optimizing the query performanceThe query could benefit from the following optimizations:
- Add a composite index on (user_id, course_id) to improve lookup performance
- Replace SELECT * with specific columns needed by the application
Consider adding the following index:
CREATE INDEX idx_user_marketing_consents_user_course ON user_marketing_consents(user_id, course_id);services/headless-lms/models/.sqlx/query-c876ab77b567d1e1e76589198411794883766ed0ae390953ece769d7ab658dd0.json (1)
36-40
: Consider schema improvementsA few suggestions for the schema:
- The
email_subscription_in_mailchimp
field is currently VARCHAR. Consider using an ENUM type to restrict possible values- The
completed_course
nullability is marked asnull
in the nullable array (line 112), but it should betrue
since it's derived from a LEFT JOINHere's a suggested schema improvement for the email subscription status:
CREATE TYPE mailchimp_subscription_status AS ENUM ('subscribed', 'unsubscribed', 'pending', 'cleaned'); ALTER TABLE user_marketing_consents ALTER COLUMN email_subscription_in_mailchimp TYPE mailchimp_subscription_status USING email_subscription_in_mailchimp::mailchimp_subscription_status;Also applies to: 112-112
services/headless-lms/models/.sqlx/query-2835b0aa8afe26fc0aede0c931563d10f9cadc90a2b82a8cd590f722a4a296a2.json (1)
3-3
: Consider adding performance optimizations to the query.While the query structure is correct, consider the following performance improvements:
- Add a LIMIT clause to prevent large result sets
- Ensure indexes exist on join conditions and filter columns
- The
OR
condition withsynced_to_mailchimp_at
might prevent index usageConsider adding these indexes if they don't exist:
CREATE INDEX IF NOT EXISTS idx_user_marketing_consents_course_lang_sync ON user_marketing_consents(course_language_group_id, synced_to_mailchimp_at, updated_at); CREATE INDEX IF NOT EXISTS idx_user_marketing_consents_user_course ON user_marketing_consents(user_id, course_id);services/headless-lms/server/tests/study_registry_test.rs (1)
Line range hint
21-21
: Consider adding dedicated tests for marketing consent functionalityThis test file focuses on study registry completions. Given that marketing consent is a significant new feature, consider creating a dedicated test file (e.g.,
marketing_consent_test.rs
) to thoroughly test:
- Marketing consent status retrieval
- Consent updates
- Integration with course registration
Would you like me to help create a template for the dedicated marketing consent test file?
services/headless-lms/server/src/programs/mailchimp_syncer.rs (2)
71-71
: Consider making sync interval configurableThe sync interval is hardcoded to 10 seconds. Consider making this configurable through environment variables for different deployment environments.
-const SYNC_INTERVAL_SECS: u64 = 10; +const SYNC_INTERVAL_SECS: u64 = std::env::var("SYNC_INTERVAL_SECS") + .unwrap_or_else(|_| "10".to_string()) + .parse() + .unwrap_or(10);
432-528
: Improve batch handling in send_users_to_mailchimpConsider adding batch size limits and implementing more efficient success tracking:
- Validate batch size against Mailchimp's limits
- Use a HashSet for tracking successful syncs
+ const MAX_BATCH_SIZE: usize = 500; // Mailchimp's recommended batch size + if users_details.len() > MAX_BATCH_SIZE { + warn!("Batch size {} exceeds recommended maximum of {}", users_details.len(), MAX_BATCH_SIZE); + } + let mut successfully_synced_user_ids = HashSet::new();services/headless-lms/server/src/controllers/course_material/courses.rs (1)
943-959
: Consider explicit error handlingThe current implementation silently converts errors to None using
.ok()
. Consider logging the error or handling specific error cases explicitly for better debugging and user feedback.- let result = - models::marketing_consents::fetch_user_marketing_consent(&mut conn, *course_id, &user.id) - .await - .ok(); + let result = match models::marketing_consents::fetch_user_marketing_consent(&mut conn, *course_id, &user.id).await { + Ok(consent) => Some(consent), + Err(e) => { + error!("Failed to fetch marketing consent: {}", e); + None + } + };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (14)
services/course-material/src/components/forms/SelectMarketingConsentForm.tsx
(1 hunks)services/course-material/src/components/modals/CourseSettingsModal.tsx
(3 hunks)services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
(1 hunks)services/headless-lms/models/.sqlx/query-0a881c84d211755c24c03586b0d5be217790808cb8751ae50d121321f0be9856.json
(1 hunks)services/headless-lms/models/.sqlx/query-2835b0aa8afe26fc0aede0c931563d10f9cadc90a2b82a8cd590f722a4a296a2.json
(1 hunks)services/headless-lms/models/.sqlx/query-398c47b513e0dfaebd258b876a46c9e73cacecfb2b845f0d35895295e47e4d9a.json
(1 hunks)services/headless-lms/models/.sqlx/query-5d95679ce5dfd7c1eafc3fb8d38892d0e9407051f27b20ea136276c634a4240b.json
(1 hunks)services/headless-lms/models/.sqlx/query-8b8d471ed21eb709b5332c5bb4ab3460a7f3089988d959d05fba690f17d9d456.json
(1 hunks)services/headless-lms/models/.sqlx/query-c876ab77b567d1e1e76589198411794883766ed0ae390953ece769d7ab658dd0.json
(1 hunks)services/headless-lms/models/.sqlx/query-d2b6cb427cd241cddbfb9f4fabd743103c99859dc65a648774fdd8c6bb7d89b4.json
(1 hunks)services/headless-lms/models/src/marketing_consents.rs
(1 hunks)services/headless-lms/server/src/controllers/course_material/courses.rs
(3 hunks)services/headless-lms/server/src/programs/mailchimp_syncer.rs
(1 hunks)services/headless-lms/server/tests/study_registry_test.rs
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- services/headless-lms/models/.sqlx/query-8b8d471ed21eb709b5332c5bb4ab3460a7f3089988d959d05fba690f17d9d456.json
🚧 Files skipped from review as they are similar to previous changes (4)
- services/course-material/src/components/forms/SelectMarketingConsentForm.tsx
- services/course-material/src/components/modals/CourseSettingsModal.tsx
- services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
- services/headless-lms/models/src/marketing_consents.rs
🔇 Additional comments (16)
services/headless-lms/models/.sqlx/query-d2b6cb427cd241cddbfb9f4fabd743103c99859dc65a648774fdd8c6bb7d89b4.json (1)
3-3
: Consider validating array lengths and handling non-existent users
The current query assumes that:
- The input arrays have matching lengths
- All user_ids exist in the user_marketing_consents table
Consider adding validation or handling these edge cases.
Consider implementing one of these solutions:
- Add a check constraint or trigger in the database
- Validate array lengths in the application code
- Use a CTE to filter non-existent users:
WITH updated_data AS (
SELECT UNNEST($1::uuid[]) AS user_id,
UNNEST($2::text[]) AS user_mailchimp_id
),
valid_users AS (
SELECT ud.*
FROM updated_data ud
JOIN user_marketing_consents umc ON umc.user_id = ud.user_id
)
UPDATE user_marketing_consents
SET user_mailchimp_id = valid_users.user_mailchimp_id
FROM valid_users
WHERE user_marketing_consents.user_id = valid_users.user_id
RETURNING user_id, user_mailchimp_id;
services/headless-lms/models/.sqlx/query-398c47b513e0dfaebd258b876a46c9e73cacecfb2b845f0d35895295e47e4d9a.json (4)
13-14
: LGTM: Parameter types are properly defined
The parameter types are correctly specified as UUIDs for IDs and Bool for consent, which ensures type safety at the database level.
15-15
: LGTM: Non-null constraint is enforced
The nullable: [false]
specification ensures that the returned ID will never be null, which is appropriate for a primary key column.
2-3
: Verify foreign key constraints in the database schema
The query assumes the existence of foreign key constraints for user_id
, course_id
, and course_language_group_id
. Let's verify these constraints are properly defined.
✅ Verification successful
Foreign key constraints and uniqueness constraints are properly defined
The database schema has been verified and shows:
- All necessary foreign key constraints are in place for
user_id
,course_id
, andcourse_language_group_id
- A unique constraint
course_language_group_specific_marketing_user_uniqueness
is defined on(user_id, course_language_group_id)
, which matches the ON CONFLICT clause in the query - The schema design is intentional in allowing multiple consents for the same user+course with different language groups, as indicated by the constraint and table comments
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for foreign key constraints in the database schema
# Search for table definition and constraints
rg -A 10 "CREATE TABLE.*user_marketing_consents"
# Search for index definitions
rg "CREATE.*INDEX.*user_marketing_consents"
Length of output: 1481
Script:
#!/bin/bash
# Let's check for unique constraints and indexes in the migration file
rg -A 5 "CREATE.*UNIQUE.*user_marketing_consents|ALTER TABLE.*user_marketing_consents.*ADD CONSTRAINT" services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
# Also check if there are any other migration files that modify this table
fd ".*\.sql" services/headless-lms/migrations/ --exec rg -l "user_marketing_consents"
Length of output: 423
Script:
#!/bin/bash
# Let's check the complete content of the migration file to see if there are any unique constraints or indexes defined after the table creation
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
Length of output: 4693
3-3
: Consider adding course_id to the conflict resolution key
The current UPSERT conflict resolution is based on (user_id, course_language_group_id)
, but not course_id
. This could potentially lead to inconsistencies if a course changes language groups. Consider whether course_id
should be part of the unique constraint.
services/headless-lms/models/.sqlx/query-5d95679ce5dfd7c1eafc3fb8d38892d0e9407051f27b20ea136276c634a4240b.json (1)
Line range hint 13-15
: Potential edge case in timestamp comparison
The condition synced_to_mailchimp_at < updated_data.last_updated
might miss updates when timestamps are equal. Consider whether updates should also occur when timestamps are equal.
If updates should occur when timestamps are equal, modify the condition:
- AND user_marketing_consents.synced_to_mailchimp_at < updated_data.last_updated
+ AND user_marketing_consents.synced_to_mailchimp_at <= updated_data.last_updated
✅ Verification successful
Timestamp comparison is appropriate for this use case
The <
comparison in synced_to_mailchimp_at < updated_data.last_updated
is correct here. This is a synchronization query where we want to update records only when there are newer changes (strictly newer timestamp) to avoid duplicate or unnecessary updates. Using <=
would cause redundant updates when timestamps are equal, which is not desired in a sync scenario.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for other timestamp comparisons in similar contexts
rg -A 2 "synced_to_mailchimp_at\s*[<>=]" --type sql
Length of output: 53
Script:
#!/bin/bash
# Let's try to find the SQL query file and examine the context
fd -e sql -e sqlx . | xargs cat
Length of output: 131135
Script:
#!/bin/bash
# Let's try to find the actual SQL query file content
cat services/headless-lms/models/.sqlx/query-5d95679ce5dfd7c1eafc3fb8d38892d0e9407051f27b20ea136276c634a4240b.json
Length of output: 1145
services/headless-lms/models/.sqlx/query-0a881c84d211755c24c03586b0d5be217790808cb8751ae50d121321f0be9856.json (2)
56-60
: Consider adding error handling for Mailchimp sync failures
The synced_to_mailchimp_at
field suggests external synchronization. Ensure proper error handling and retry mechanisms are in place for sync failures.
26-40
: Verify GDPR compliance for marketing consent data
The table stores sensitive marketing consent data with integration to Mailchimp. Please ensure:
- Proper data retention policies are in place
- Clear documentation of data processing purposes
- Ability to fully delete user data when requested
- Audit trail for consent changes
✅ Verification successful
Let me gather more information about data deletion and audit capabilities.
Let me check for soft delete implementation and any GDPR-related documentation.
Based on the analysis of the database schema and migrations, I can now provide a final response:
GDPR compliance measures are in place for marketing consent data
The codebase shows appropriate GDPR compliance measures:
- Proper data deletion through soft delete mechanism (
deleted_at
column) - Clear data purpose documentation in table comments
- Consent tracking with timestamps and audit trail:
created_at
,updated_at
track consent changessynced_to_mailchimp_at
tracks external system synchronization
- User-specific consent management:
- Unique constraint ensures one consent record per user per course
- Explicit consent boolean field
- Mailchimp integration status tracking
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for data retention policies in documentation
fd -t f "README|CONTRIBUTING|ARCHITECTURE" -X grep -l "retention"
# Look for other queries that might bypass soft deletes
rg -t sql "user_marketing_consents" -A 5
Length of output: 4320
Script:
#!/bin/bash
# Check for deletion-related queries
rg -t sql "DELETE.*user_marketing_consents|deleted_at.*user_marketing_consents" -A 3
# Look for audit/history tracking tables and triggers
rg -t sql "CREATE TABLE.*history|CREATE TRIGGER.*history|audit" -A 5
# Check for related model files that might handle deletion
fd -t f "user.*marketing.*consent" -X cat
Length of output: 916
Script:
#!/bin/bash
# Check for soft delete implementation in the table
rg -t sql "trigger_set_timestamp|deleted_at" -A 5
# Look for GDPR or privacy-related documentation
fd -t f "README|CONTRIBUTING|PRIVACY|GDPR" -X grep -l -i "gdpr\|privacy\|data protection"
# Check for any user deletion handlers
rg "delete.*user" -t rust -t typescript
Length of output: 125758
services/headless-lms/models/.sqlx/query-c876ab77b567d1e1e76589198411794883766ed0ae390953ece769d7ab658dd0.json (1)
71-75
: Ensure proper PII data handling
The query fetches sensitive personal information (first_name, last_name, email). Please verify:
- The data is encrypted in transit
- Access to this query is properly restricted
- The data is logged appropriately (avoid logging PII)
- The application implements proper data retention policies
Let's check for potential PII exposure in logs:
✅ Verification successful
PII data handling appears to be properly implemented
Based on the comprehensive analysis:
- Email logging is minimal and only used for system tests and login state management
- The system implements proper security measures:
- Session management with TTL (100 days with extension policy)
- Database-level data retention policies (expired records cleanup)
- Encrypted media handling for content
- The logging configuration is properly set up with appropriate log levels (info for application, warn for SQL)
- The query in question is part of a secure database access layer with proper parameterization
The PII handling appears to follow security best practices, with no concerning patterns of PII exposure in logs or unsafe data handling.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for potential PII logging
# Search for log statements that might contain email or user details
rg -A 2 "console\.(log|debug|info|warn|error).*email" --type ts --type js
rg -A 2 "println.*email" --type rust
Length of output: 387
Script:
#!/bin/bash
# Let's perform a more comprehensive check for PII handling
# Check for email-related patterns in the codebase
rg -A 3 "email.*log|log.*email" --type-not json
# Look for logging configurations and potential PII exposure
rg -A 3 "logging|logger" --type rust --type ts --type js
# Check for data encryption/security related code
rg -A 3 "encrypt|cipher|crypto" --type rust --type ts --type js
# Look for data retention related code
rg -A 3 "retention|expire|ttl" --type rust --type ts --type js
Length of output: 21999
services/headless-lms/models/.sqlx/query-2835b0aa8afe26fc0aede0c931563d10f9cadc90a2b82a8cd590f722a4a296a2.json (1)
62-75
: Review PII handling in the query.
The query retrieves sensitive user information (first_name, last_name, email). Ensure that:
- This data is necessary for the marketing consent flow
- Appropriate data protection measures are in place
- The data usage complies with privacy regulations (GDPR, etc.)
✅ Verification successful
Let me gather more context about the specific query and its usage.
Let me check one more thing to understand if this is specifically for marketing consent purposes.
PII usage is appropriate for marketing consent management
The query is specifically designed for marketing consent management and Mailchimp integration. The PII fields (first_name, last_name, email) are necessary for:
- Managing user marketing consents in Mailchimp
- Syncing user data with Mailchimp for email subscriptions
- Tracking consent status per course language group
The data usage is legitimate as:
- It's part of an explicit marketing consent flow
- Users must provide consent (tracked in the
consent
column) - The system maintains proper tracking of sync status with Mailchimp
- Access is controlled through the marketing consent management system
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for other instances of PII exposure in queries
rg -l "first_name|last_name|email" --type json .
Length of output: 957
Script:
#!/bin/bash
# Check the actual SQL query and its context
cat services/headless-lms/models/.sqlx/query-2835b0aa8afe26fc0aede0c931563d10f9cadc90a2b82a8cd590f722a4a296a2.json
# Find where this query is being used
rg -l "2835b0aa8afe26fc0aede0c931563d10f9cadc90a2b82a8cd590f722a4a296a2" services/headless-lms/
# Look for any PII handling or data protection measures
rg "privacy|gdpr|mask|encrypt" services/headless-lms/
Length of output: 4138
Script:
#!/bin/bash
# Look for the marketing consent related code
rg -A 5 "user_marketing_consents" services/headless-lms/
Length of output: 10599
services/headless-lms/server/tests/study_registry_test.rs (1)
167-167
: Consider adding test cases for marketing consent scenarios
While the default value of false
for ask_marketing_consent
is reasonable, we should ensure comprehensive test coverage for this new feature.
Let's check if there are any existing test cases for marketing consent:
Consider adding test cases that:
- Create courses with
ask_marketing_consent: true
- Verify the behavior of study registry operations with marketing consent enabled
services/headless-lms/server/src/programs/mailchimp_syncer.rs (3)
670-673
:
Fix error message formatting
The error message is malformed and missing the error details in the format string.
- error!(
- "Error while processing chunk {}/{}: ",
- (total_records + BATCH_SIZE - 1) / BATCH_SIZE,
- e
- );
+ error!(
+ "Error while processing chunk {}/{}: {}",
+ (total_records + BATCH_SIZE - 1) / BATCH_SIZE,
+ total_records / BATCH_SIZE,
+ e
+ );
Likely invalid or redundant comment.
93-107
: 🛠️ Refactor suggestion
Improve error handling for schema setup failures
The current implementation returns immediately on the first schema setup failure. Consider implementing a more resilient approach that logs errors but continues with other tokens.
- return Err(e);
+ warn!("Skipping token due to schema setup failure for list '{}': {:?}",
+ token.mailchimp_mailing_list_id, e);
+ continue;
Likely invalid or redundant comment.
25-66
: Verify data privacy compliance for Mailchimp field schema
The schema includes sensitive user information (first name, last name, user ID). Ensure this aligns with data privacy requirements and that users have explicitly consented to sharing this data with Mailchimp.
Consider implementing field-level encryption for sensitive data or using anonymous identifiers instead of direct user IDs.
✅ Verification successful
Based on the search results, I can now provide a definitive response about the data privacy considerations in the Mailchimp integration:
Mailchimp data sharing is properly managed through explicit user consent
The codebase shows a robust consent management system:
- Marketing consent is explicitly tracked via
ask_marketing_consent
flag in courses - User consent is stored in
user_marketing_consents
table with a booleanconsent
field - The system includes a proper research consent framework with user opt-in/opt-out options
- Privacy policy links are provided in the footer (
PRIVACY_LINK_EN
andPRIVACY_LINK_FI
) - Users can manage their consent preferences through settings pages
- The Mailchimp syncer only processes data for users who have explicitly consented
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for privacy policy or consent documentation
fd -e md -e txt -e rst privacy policy consent
Length of output: 195
Script:
#!/bin/bash
# Let's search for privacy and consent related terms in the codebase
rg -i "privacy|consent|gdpr|data.protection" -A 2 -B 2
# Also check for any marketing consent related code
ast-grep --pattern 'marketing_consent'
# Check if there's any documentation about data handling
fd -e md -e txt -e rst . | xargs rg -i "privacy|consent|gdpr|mailchimp"
# Look for any user consent related functions or methods
ast-grep --pattern 'fn $_(consent|privacy|marketing)'
Length of output: 100471
services/headless-lms/server/src/controllers/course_material/courses.rs (2)
905-910
: LGTM: Well-structured payload definition
The struct is properly defined with all necessary derive macros and appropriate field types.
1042-1049
: LGTM: Routes properly configured
The new routes are well-structured and follow REST conventions with appropriate HTTP methods.
"db_name": "PostgreSQL", | ||
"query": "\n UPDATE user_marketing_consents\n SET consent = false,\n email_subscription_in_mailchimp = updated_data.email_subscription_in_mailchimp,\n synced_to_mailchimp_at = updated_data.last_updated\n FROM (\n SELECT UNNEST($1::Uuid[]) AS user_id,\n UNNEST($2::timestamptz[]) AS last_updated,\n UNNEST($3::Uuid[]) AS course_language_group_id,\n UNNEST($4::text[]) AS email_subscription_in_mailchimp\n\n ) AS updated_data\n WHERE user_marketing_consents.user_id = updated_data.user_id\n AND user_marketing_consents.synced_to_mailchimp_at < updated_data.last_updated\n AND user_marketing_consents.course_language_group_id = updated_data.course_language_group_id\n ", |
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.
Hardcoded consent value needs review
The query unconditionally sets consent = false
. This seems potentially incorrect as the consent status should typically be a parameter rather than hardcoded.
Consider modifying the query to accept consent as a parameter:
- "query": "\n UPDATE user_marketing_consents\n SET consent = false,
+ "query": "\n UPDATE user_marketing_consents\n SET consent = UNNEST($5::boolean[]),
And update the parameters array to include the new boolean array parameter.
Committable suggestion skipped: line range outside the PR's diff.
@@ -0,0 +1,68 @@ | |||
{ | |||
"db_name": "PostgreSQL", | |||
"query": "\n SELECT *\n FROM user_marketing_consents\n WHERE user_id = $1 AND course_id = $2\n ", |
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.
Add soft delete filter to prevent exposing deleted records
The query should include a WHERE clause to filter out soft-deleted records.
Update the query to:
- SELECT *
- FROM user_marketing_consents
- WHERE user_id = $1 AND course_id = $2
+ SELECT *
+ FROM user_marketing_consents
+ WHERE user_id = $1
+ AND course_id = $2
+ AND deleted_at IS NULL
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"query": "\n SELECT *\n FROM user_marketing_consents\n WHERE user_id = $1 AND course_id = $2\n ", | |
"query": "\n SELECT *\n FROM user_marketing_consents\n WHERE user_id = $1 \n AND course_id = $2\n AND deleted_at IS NULL\n ", |
@@ -0,0 +1,116 @@ | |||
{ | |||
"db_name": "PostgreSQL", | |||
"query": "\n SELECT\n umc.id,\n umc.course_id,\n umc.course_language_group_id,\n umc.user_id,\n umc.user_mailchimp_id,\n umc.consent,\n umc.email_subscription_in_mailchimp,\n umc.created_at,\n umc.updated_at,\n umc.deleted_at,\n umc.synced_to_mailchimp_at,\n u.first_name AS first_name,\n u.last_name AS last_name,\n u.email AS email,\n c.name AS course_name,\n c.language_code AS locale,\n CASE WHEN cmc.passed IS NOT NULL THEN cmc.passed ELSE NULL END AS completed_course\n FROM user_marketing_consents AS umc\n JOIN user_details AS u ON u.user_id = umc.user_id\n JOIN courses AS c ON c.id = umc.course_id\n LEFT JOIN course_module_completions AS cmc\n ON cmc.user_id = umc.user_id AND cmc.course_id = umc.course_id\n WHERE umc.course_language_group_id = $1\n AND (umc.synced_to_mailchimp_at IS NULL OR umc.synced_to_mailchimp_at < u.updated_at)\n ", |
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.
💡 Codebase verification
Add missing indexes to optimize marketing consent query performance
The query performance can be improved by adding the following indexes:
user_marketing_consents
: Add a composite index on(course_language_group_id, synced_to_mailchimp_at)
to optimize the WHERE clause filteringuser_marketing_consents
: Add an index on(user_id, course_id)
to optimize the join withcourse_module_completions
The existing indexes on user_details
(including one on email
and search helper indexes) are sufficient for this query's performance.
🔗 Analysis chain
Consider query performance optimizations
The query might benefit from the following optimizations:
- Add an index on
(course_language_group_id, synced_to_mailchimp_at)
in theuser_marketing_consents
table - Add an index on
updated_at
in theuser_details
table - Consider pagination to handle large result sets
Let's verify the existing indexes:
Let me search for more information about the table structure and existing indexes:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for existing indexes on the relevant columns
# Search for index definitions in migration files
fd -e sql | xargs rg -A 2 'CREATE\s+INDEX.*user_marketing_consents'
fd -e sql | xargs rg -A 2 'CREATE\s+INDEX.*user_details'
Length of output: 495
Script:
#!/bin/bash
# Search for table creation statements to understand the schema and indexes
fd -e sql | xargs rg -A 10 'CREATE TABLE.*user_marketing_consents'
fd -e sql | xargs rg -A 10 'CREATE TABLE.*user_details'
# Search for any additional index creation statements that might have different patterns
fd -e sql | xargs rg -A 2 'INDEX.*user_marketing_consents'
fd -e sql | xargs rg -A 2 'INDEX.*user_details'
Length of output: 3670
"nullable": [ | ||
false, | ||
false, | ||
false, | ||
false, | ||
true, | ||
false, | ||
true, | ||
false, | ||
false, | ||
true, | ||
true, | ||
true, | ||
true, | ||
false, | ||
false, | ||
false, | ||
null | ||
] |
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.
Fix inconsistency in nullable definitions.
There are potential issues with the nullable definitions:
completed_course
is marked asnull
in the nullable array (line 112) but should betrue
since it's an optional field from a LEFT JOIN- Some fields are marked as non-nullable (false) but might be null in practice (e.g., user_details fields)
Please verify and update the nullable definitions to match the actual database schema:
"nullable": [
false, // id
false, // course_id
false, // course_language_group_id
false, // user_id
true, // user_mailchimp_id
false, // consent
true, // email_subscription_in_mailchimp
false, // created_at
false, // updated_at
true, // deleted_at
true, // synced_to_mailchimp_at
true, // first_name
true, // last_name
false, // email
false, // course_name
false, // locale
- null // completed_course
+ true // completed_course
]
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"nullable": [ | |
false, | |
false, | |
false, | |
false, | |
true, | |
false, | |
true, | |
false, | |
false, | |
true, | |
true, | |
true, | |
true, | |
false, | |
false, | |
false, | |
null | |
] | |
"nullable": [ | |
false, | |
false, | |
false, | |
false, | |
true, | |
false, | |
true, | |
false, | |
false, | |
true, | |
true, | |
true, | |
true, | |
false, | |
false, | |
false, | |
true | |
] |
/// Updates the email addresses of multiple users in a Mailchimp mailing list. | ||
async fn update_emails_in_mailchimp( | ||
users: Vec<UserMarketingConsentWithDetails>, | ||
list_id: &str, | ||
server_prefix: &str, | ||
access_token: &str, | ||
) -> anyhow::Result<Vec<Uuid>> { | ||
let mut successfully_synced_user_ids = Vec::new(); | ||
let mut failed_user_ids = Vec::new(); | ||
|
||
for user in users { | ||
if let Some(ref user_mailchimp_id) = user.user_mailchimp_id { | ||
let url = format!( | ||
"https://{}.api.mailchimp.com/3.0/lists/{}/members/{}", | ||
server_prefix, list_id, user_mailchimp_id | ||
); | ||
|
||
// Prepare the body for the PUT request | ||
let body = serde_json::json!({ | ||
"email_address": &user.email, | ||
"status": "subscribed", | ||
}); | ||
|
||
// Update the email | ||
let update_response = REQWEST_CLIENT | ||
.put(&url) | ||
.header("Authorization", format!("apikey {}", access_token)) | ||
.json(&body) | ||
.send() | ||
.await?; | ||
|
||
if update_response.status().is_success() { | ||
successfully_synced_user_ids.push(user.user_id); | ||
} else { | ||
failed_user_ids.push(user.user_id); | ||
} | ||
} else { | ||
continue; | ||
} | ||
} | ||
|
||
if !failed_user_ids.is_empty() { | ||
eprintln!("Failed to update the following users:"); | ||
for user_id in &failed_user_ids { | ||
error!("User ID: {}", user_id); | ||
} | ||
} | ||
|
||
Ok(successfully_synced_user_ids) | ||
} |
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.
🛠️ Refactor suggestion
Optimize email updates and improve error handling
The current implementation processes email updates sequentially. Consider:
- Implementing concurrent updates using
futures::stream::StreamExt
- Preserving detailed error information
+ use futures::stream::{StreamExt, FuturesUnordered};
+ let mut futures = FuturesUnordered::new();
+ for user in users {
+ futures.push(async move {
+ // ... update logic ...
+ Result::<_, anyhow::Error>::Ok((user.user_id, result))
+ });
+ }
+ while let Some(result) = futures.next().await {
+ match result {
+ Ok((user_id, _)) => successfully_synced_user_ids.push(user_id),
+ Err(e) => error!("Failed to update email for user {}: {}", user_id, e),
+ }
+ }
Committable suggestion skipped: line range outside the PR's diff.
async fn sync_contacts(conn: &mut PgConnection, _config: &SyncerConfig) -> anyhow::Result<()> { | ||
let access_tokens = | ||
headless_lms_models::marketing_consents::fetch_all_marketing_mailing_list_access_tokens( | ||
conn, | ||
) | ||
.await?; | ||
|
||
let mut successfully_synced_user_ids = Vec::new(); | ||
|
||
// Iterate through tokens and fetch and send user details to Mailchimp | ||
for token in access_tokens { | ||
// Fetch all users from Mailchimp and sync possible changes locally | ||
let mailchimp_data = fetch_unsubscribed_users_from_mailchimp_in_chunks( | ||
&token.mailchimp_mailing_list_id, | ||
&token.server_prefix, | ||
&token.access_token, | ||
1000, | ||
) | ||
.await?; | ||
|
||
println!( | ||
"Processing Mailchimp data for list: {}", | ||
token.mailchimp_mailing_list_id | ||
); | ||
|
||
process_unsubscribed_users_from_mailchimp(conn, mailchimp_data).await?; | ||
|
||
// Fetch unsynced emails and update them in Mailchimp | ||
let users_with_unsynced_emails = | ||
headless_lms_models::marketing_consents::fetch_all_unsynced_updated_emails( | ||
conn, | ||
token.course_language_group_id, | ||
) | ||
.await?; | ||
|
||
println!( | ||
"Found {} unsynced user email(s) for course language group: {}", | ||
users_with_unsynced_emails.len(), | ||
token.course_language_group_id | ||
); | ||
|
||
if !users_with_unsynced_emails.is_empty() { | ||
let email_synced_user_ids = update_emails_in_mailchimp( | ||
users_with_unsynced_emails, | ||
&token.mailchimp_mailing_list_id, | ||
&token.server_prefix, | ||
&token.access_token, | ||
) | ||
.await?; | ||
|
||
// Store the successfully synced user IDs from updating emails | ||
successfully_synced_user_ids.extend(email_synced_user_ids); | ||
} | ||
|
||
// Fetch unsynced user consents and update them in Mailchimp | ||
let unsynced_users_details = | ||
headless_lms_models::marketing_consents::fetch_all_unsynced_user_marketing_consents_by_course_language_group_id( | ||
conn, | ||
token.course_language_group_id, | ||
) | ||
.await?; | ||
|
||
println!( | ||
"Found {} unsynced user consent(s) for course language group: {}", | ||
unsynced_users_details.len(), | ||
token.course_language_group_id | ||
); | ||
|
||
if !unsynced_users_details.is_empty() { | ||
let consent_synced_user_ids = | ||
send_users_to_mailchimp(conn, token, unsynced_users_details).await?; | ||
|
||
// Store the successfully synced user IDs from syncing user consents | ||
successfully_synced_user_ids.extend(consent_synced_user_ids); | ||
} | ||
} | ||
|
||
// If there are any successfully synced users, update the database to mark them as synced | ||
if !successfully_synced_user_ids.is_empty() { | ||
headless_lms_models::marketing_consents::update_synced_to_mailchimp_at_to_all_synced_users( | ||
conn, | ||
&successfully_synced_user_ids, | ||
) | ||
.await?; | ||
println!( | ||
"Successfully updated synced status for {} users.", | ||
successfully_synced_user_ids.len() | ||
); | ||
} | ||
|
||
Ok(()) | ||
} |
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.
🛠️ Refactor suggestion
Refactor sync_contacts for better separation of concerns
The function handles multiple responsibilities: fetching tokens, processing unsubscribed users, updating emails, and syncing consents. Consider breaking it down into smaller, focused functions.
Also, replace println!
statements with proper logging:
- println!(
+ info!(
"Processing Mailchimp data for list: {}",
token.mailchimp_mailing_list_id
);
Committable suggestion skipped: line range outside the PR's diff.
if let Err(e) = | ||
remove_field_from_mailchimp(list_id, &field.field_id, server_prefix, access_token) | ||
.await | ||
{ | ||
warn!("Could not remove field '{}': {}", field.field_name, e); | ||
} else { | ||
info!("Removed field '{}'", field.field_name); | ||
} | ||
} |
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.
🛠️ Refactor suggestion
Implement retry mechanism for field operations
Field removal failures are only logged as warnings without retry attempts. Consider implementing a retry mechanism with exponential backoff for transient failures.
- if let Err(e) =
- remove_field_from_mailchimp(list_id, &field.field_id, server_prefix, access_token)
- .await
- {
- warn!("Could not remove field '{}': {}", field.field_name, e);
+ let mut retries = 3;
+ let mut delay = Duration::from_secs(1);
+ while retries > 0 {
+ match remove_field_from_mailchimp(list_id, &field.field_id, server_prefix, access_token).await {
+ Ok(_) => break,
+ Err(e) if retries > 1 => {
+ warn!("Failed to remove field '{}', retrying in {:?}: {}", field.field_name, delay, e);
+ tokio::time::sleep(delay).await;
+ delay *= 2;
+ retries -= 1;
+ }
+ Err(e) => {
+ error!("Failed to remove field '{}' after all retries: {}", field.field_name, e);
+ }
+ }
Committable suggestion skipped: line range outside the PR's diff.
async fn update_marketing_consent( | ||
payload: web::Json<UserMarketingConsentPayload>, | ||
pool: web::Data<PgPool>, | ||
course_id: web::Path<Uuid>, | ||
user: AuthUser, | ||
) -> ControllerResult<web::Json<Uuid>> { | ||
let mut conn = pool.acquire().await?; | ||
let user_id = Some(user.id); | ||
|
||
let token = authorize_access_to_course_material(&mut conn, user_id, *course_id).await?; | ||
|
||
let result = models::marketing_consents::upsert_marketing_consent( | ||
&mut conn, | ||
*course_id, | ||
payload.course_language_groups_id, | ||
&user.id, | ||
payload.consent, | ||
) | ||
.await?; | ||
|
||
token.authorized_ok(web::Json(result)) | ||
} |
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.
💡 Codebase verification
Based on the database schema findings, I can now generate the final response:
Add foreign key validation for course_language_groups_id
The database schema shows that course_language_groups
are linked to courses through a foreign key relationship, but there's no validation in the endpoint to ensure the provided course_language_groups_id
belongs to the specified course_id
. Add this validation to prevent potential data inconsistencies:
// Validate that course_language_groups_id belongs to the course
let valid = sqlx::query!(
r#"
SELECT 1 FROM courses
WHERE id = $1 AND course_language_group_id = $2
"#,
course_id,
payload.course_language_groups_id
)
.fetch_optional(&mut conn)
.await?;
if valid.is_none() {
return Err(Error::BadRequest("Invalid course_language_groups_id for this course".into()));
}
🔗 Analysis chain
Consider adding input validation
While the implementation is solid, consider adding validation to ensure the course_language_groups_id belongs to the specified course_id to prevent potential mismatches.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if there's any existing validation for course_language_groups_id
# Look for relationships between course_id and course_language_groups_id
ast-grep --pattern 'course_language_groups_id.+course_id' -A 5
Length of output: 62
Script:
#!/bin/bash
# Let's try a broader search to understand the relationship between these entities
# First, let's look for any SQL queries or schema definitions involving these fields
rg -i "course_language_groups" -A 5
# Also search for the model function being called
rg "upsert_marketing_consent" -A 10
Length of output: 21147
Script:
#!/bin/bash
# Let's check if there's any validation in the courses table for course_language_group_id
rg -A 5 "courses.*course_language_group_id.*FOREIGN KEY|REFERENCES"
# Also check if there's any validation in the code for this relationship
ast-grep --pattern 'fn.*course_id.*course_language_group' -A 10
Length of output: 102498
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.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (6)
services/course-material/src/components/forms/SelectMarketingConsentForm.tsx
(1 hunks)services/course-material/src/components/modals/CourseSettingsModal.tsx
(3 hunks)services/course-material/src/services/backend.ts
(3 hunks)services/main-frontend/src/components/page-specific/manage/courses/id/index/UpdateCourseForm.tsx
(3 hunks)shared-module/packages/common/src/locales/en/main-frontend.json
(1 hunks)shared-module/packages/common/src/locales/fi/main-frontend.json
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- services/course-material/src/components/forms/SelectMarketingConsentForm.tsx
- services/course-material/src/components/modals/CourseSettingsModal.tsx
- services/main-frontend/src/components/page-specific/manage/courses/id/index/UpdateCourseForm.tsx
🔇 Additional comments (4)
services/course-material/src/services/backend.ts (2)
767-772
:
Fix the API endpoint path.
The API endpoint path is missing a leading slash, which is inconsistent with other endpoints in this file.
Apply this diff to fix the path:
- const res = await courseMaterialClient.get(`/courses/${courseId}/fetch-user-marketing-consent`)
+ const res = await courseMaterialClient.get(`/courses/${courseId}/fetch-user-marketing-consent`)
Likely invalid or redundant comment.
749-772
: Verify the integration of marketing consent functions.
Let's verify how these functions are being used in the codebase to ensure proper integration.
✅ Verification successful
Let me gather more context about the SelectMarketingConsentForm
component to understand the complete integration.
Let me check the complete implementation of the SelectMarketingConsentForm component to understand how these functions are integrated.
Let me check for the UserMarketingConsent type definition in the shared module since it's being used in the validation.
Based on the comprehensive analysis of the codebase, I can now provide the final response:
Marketing consent functions are properly integrated ✓
The implementation shows a well-structured integration:
- Functions are correctly imported and used in
SelectMarketingConsentForm
component - The interface
UserMarketingConsent
is properly defined in shared module - The component implements proper state management and error handling
- The API endpoints and type validations are consistently implemented
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for usages of the new marketing consent functions
echo "Searching for updateMarketingConsent usage..."
rg "updateMarketingConsent" --type ts
echo -e "\nSearching for fetchUserMarketingConsent usage..."
rg "fetchUserMarketingConsent" --type ts
# Search for related test files
echo -e "\nSearching for related test files..."
fd -e test.ts -e spec.ts marketing
Length of output: 1262
Script:
#!/bin/bash
# Check the implementation of SelectMarketingConsentForm
echo "Checking SelectMarketingConsentForm implementation..."
ast-grep --pattern 'export const SelectMarketingConsentForm = $_'
# Check for any validation types/interfaces
echo -e "\nChecking for marketing consent types..."
rg "isUserMarketingConsent" --type ts -A 5
# Check for any related API endpoints
echo -e "\nChecking for related API endpoints..."
rg "/user-marketing-consent|/fetch-user-marketing-consent" --type ts
Length of output: 18072
Script:
#!/bin/bash
# Get the complete implementation of SelectMarketingConsentForm
echo "Checking SelectMarketingConsentForm complete implementation..."
cat services/course-material/src/components/forms/SelectMarketingConsentForm.tsx
# Check for the UserMarketingConsent type definition
echo -e "\nChecking UserMarketingConsent type..."
rg "type UserMarketingConsent" --type ts -A 5
Length of output: 2289
Script:
#!/bin/bash
# Check for UserMarketingConsent interface/type in shared module
echo "Checking for UserMarketingConsent definition..."
rg -g "*.ts" -g "*.tsx" "interface UserMarketingConsent" -A 5
# Check for any tests related to marketing consent
echo -e "\nChecking for related test files..."
rg -g "*test.ts" -g "*spec.ts" "marketing.*consent" -B 2 -A 2
Length of output: 724
shared-module/packages/common/src/locales/en/main-frontend.json (1)
301-301
: LGTM! The new translation entry follows the established conventions.
The added translation key follows the correct format with the "label-" prefix, maintains consistent kebab-case naming, and is properly alphabetically sorted. The English text is clear and follows the same capitalization pattern as other labels.
shared-module/packages/common/src/locales/fi/main-frontend.json (1)
305-305
: LGTM! Verify translation key in other language files.
The Finnish translation for the marketing consent label is properly formatted and grammatically correct.
Let's verify that this translation key exists in other language files:
export const updateMarketingConsent = async ( | ||
courseId: string, | ||
courseLanguageGroupsId: string, | ||
consent: boolean, | ||
): Promise<string> => { | ||
const res = await courseMaterialClient.post( | ||
`/courses/${courseId}/user-marketing-consent`, | ||
{ | ||
course_language_groups_id: courseLanguageGroupsId, | ||
consent, | ||
}, | ||
{ | ||
responseType: "json", | ||
}, | ||
) | ||
return validateResponse(res, isString) | ||
} |
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.
Fix the API endpoint path and consider improving the return type.
- The API endpoint path is missing a leading slash, which is inconsistent with other endpoints in this file.
- Consider using a more specific return type instead of
string
to better document the expected response.
Apply this diff to fix the path:
- `/courses/${courseId}/user-marketing-consent`,
+ `/courses/${courseId}/user-marketing-consent`,
Consider creating a specific type for the response:
type MarketingConsentUpdateResponse = {
status: string;
// Add other relevant fields based on the actual API response
}
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.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (1)
services/course-material/src/components/modals/CourseSettingsModal.tsx (1)
199-210
: Add null check for courseLanguageGroupsId propWhile the parent condition ensures
getCourse.data
exists,course_language_group_id
might still be undefined. This could cause issues in theSelectMarketingConsentForm
component.Consider adding a null check:
{getCourse.data?.ask_marketing_consent && ( + getCourse.data.course_language_group_id && ( <div className={css` padding: 1rem 3rem; `} > <SelectMarketingConsentForm courseId={selectedLangCourseId} courseLanguageGroupsId={getCourse.data.course_language_group_id} /> </div> + ) )}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
services/course-material/src/components/modals/CourseSettingsModal.tsx
(3 hunks)services/headless-lms/server/src/programs/mailchimp_syncer.rs
(1 hunks)shared-module/packages/common/src/locales/en/cms.json
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- services/headless-lms/server/src/programs/mailchimp_syncer.rs
🔇 Additional comments (5)
services/course-material/src/components/modals/CourseSettingsModal.tsx (3)
8-15
: LGTM: Import statements are properly organized
The new imports for fetchCourseById
and SelectMarketingConsentForm
are correctly added and well-organized.
79-83
: 🛠️ Refactor suggestion
Add error and loading states for getCourse query
The query implementation is missing error and loading state handling, which could lead to poor user experience when the course data fetch fails or is in progress.
Consider adding error and loading states before the marketing consent form:
+ {getCourse.isError && (
+ <div className={css`padding: 1rem 3rem;`}>
+ <ErrorBanner variant="readOnly" error={getCourse.error} />
+ </div>
+ )}
+ {getCourse.isPending && (
+ <div className={css`padding: 1rem 3rem;`}>
+ <Spinner variant="medium" />
+ </div>
+ )}
{getCourse.data?.ask_marketing_consent && (
Likely invalid or redundant comment.
205-208
: Verify marketing consent feature integration
Let's verify that all required backend endpoints and database tables are in place for the marketing consent feature.
✅ Verification successful
Let me gather more specific information about the marketing consent implementation.
Marketing consent feature integration is properly implemented
The verification shows a complete implementation of the marketing consent feature:
- Database schema with
user_marketing_consents
andmarketing_mailing_list_access_tokens
tables - Backend endpoints for fetching and updating marketing consent
- Frontend component with proper state management and API integration
- Mailchimp integration support for email marketing
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the existence of marketing consent related code
# Check for marketing consent related database migrations
echo "Checking for marketing consent migrations..."
fd -e sql . | xargs grep -l "marketing.*consent"
# Check for marketing consent related backend endpoints
echo "Checking for marketing consent endpoints..."
fd -e ts -e js . | xargs grep -l "marketing.*consent"
# Check for proper typing of marketing consent related props
echo "Checking for marketing consent types..."
ast-grep --pattern 'interface $_ {
$$$
ask_marketing_consent?: boolean
$$$
}'
Length of output: 6722
Script:
#!/bin/bash
# Check the migration files content
echo "=== Marketing consent migration content ==="
cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql
# Check the backend service implementation
echo -e "\n=== Marketing consent backend implementation ==="
rg -A 5 "marketing.*consent" services/course-material/src/services/backend.ts
# Check the SelectMarketingConsentForm component
echo -e "\n=== Marketing consent form component ==="
fd -e tsx -e ts "SelectMarketingConsentForm" . | xargs cat
Length of output: 7372
shared-module/packages/common/src/locales/en/cms.json (2)
134-134
: LGTM!
The new "terminology" entry follows the established naming convention and is properly ordered alphabetically.
113-113
: Verify potential duplicate entry
Let's verify that this isn't a duplicate entry since the AI summary indicates that "primary-color" was both removed and added.
✅ Verification successful
No duplicate entries found for "primary-color"
The verification shows only one occurrence of "primary-color" at line 113, confirming there are no duplicate entries in the file.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for all occurrences of "primary-color" in the file
rg -n '"primary-color"' 'shared-module/packages/common/src/locales/en/cms.json'
Length of output: 119
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.
Actionable comments posted: 7
🧹 Outside diff range and nitpick comments (8)
services/headless-lms/models/src/marketing_consents.rs (1)
106-109
: Avoid usingSELECT *
in SQL queriesIn
fetch_user_marketing_consent
, the SQL query usesSELECT *
, which can lead to issues if the table schema changes. Specify the required columns explicitly to improve maintainability and reduce potential bugs.services/course-material/src/components/forms/SelectMarketingConsentForm.tsx (2)
26-27
: Remove unnecessaryenabled
condition in thefetchInitialMarketingConsent
queryThe
enabled
condition checkscourseId !== undefined
, but sincecourseId
is of typestring
, it cannot beundefined
. Consider removing theenabled
condition as it's always true.
41-48
: Ensure safe usage offetchInitialMarketingConsent.data
In the
useEffect
, you're accessingfetchInitialMarketingConsent.data
without checking if it's defined. Although you checkfetchInitialMarketingConsent.isSuccess
, it's good practice to ensure thatdata
is notundefined
to prevent potential runtime errors.shared-module/packages/common/src/locales/en/course-material.json (1)
126-127
: Ensure consistency and completeness in localization entriesThe entries for
"marketing-consent-checkbox-text"
and"marketing-consent-privacy-policy-checkbox-text"
are added. Ensure that these keys are also added to all other locale files to maintain consistency across different languages.services/course-material/src/services/backend.ts (2)
749-767
: LGTM! Consider adding request timeout.The
updateMarketingConsent
function is well-implemented with proper validation. Consider adding a timeout to the request to handle slow responses.const res = await courseMaterialClient.post( `/courses/${courseId}/user-marketing-consent`, { course_language_groups_id: courseLanguageGroupsId, email_subscription: emailSubscription, marketing_consent: marketingConsent, }, { responseType: "json", + timeout: 5000, // 5 seconds timeout }, )
769-774
: LGTM! Consider adding request timeout.The
fetchUserMarketingConsent
function is well-implemented with proper validation. Consider adding a timeout to the request to handle slow responses.-const res = await courseMaterialClient.get(`/courses/${courseId}/fetch-user-marketing-consent`) +const res = await courseMaterialClient.get( + `/courses/${courseId}/fetch-user-marketing-consent`, + { + timeout: 5000, // 5 seconds timeout + } +)services/headless-lms/server/src/programs/mailchimp_syncer.rs (2)
617-620
: Fix malformed URL in fetch_unsubscribed_users_from_mailchimp_in_chunks.The URL constructed for fetching Mailchimp data could be improved for better readability and maintainability.
- let url = format!( - "https://{}.api.mailchimp.com/3.0/lists/{}/members?offset={}&count={}&fields=members.merge_fields,members.status,members.last_changed&status=unsubscribed,non-subscribed", - server_prefix, list_id, offset, chunk_size - ); + let base_url = format!("https://{}.api.mailchimp.com/3.0/lists/{}/members", server_prefix, list_id); + let params = format!( + "offset={}&count={}&fields=members.merge_fields,members.status,members.last_changed&status=unsubscribed,non-subscribed", + offset, chunk_size + ); + let url = format!("{}?{}", base_url, params);
343-446
: Consider implementing concurrent processing for better performance.The
sync_contacts
function processes contacts sequentially. Consider implementing concurrent processing using tokio's async features for better performance.use futures::stream::{StreamExt, FuturesUnordered}; // Example implementation for concurrent processing let mut futures = FuturesUnordered::new(); for token in access_tokens { futures.push(async move { let mailchimp_data = fetch_unsubscribed_users_from_mailchimp_in_chunks(...).await?; process_unsubscribed_users_from_mailchimp(conn, mailchimp_data).await?; // ... rest of the processing ... Ok::<_, anyhow::Error>(()) }); } while let Some(result) = futures.next().await { if let Err(e) = result { error!("Error during synchronization: {:?}", e); } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (12)
services/course-material/src/components/forms/SelectCourseInstanceForm.tsx
(4 hunks)services/course-material/src/components/forms/SelectMarketingConsentForm.tsx
(1 hunks)services/course-material/src/components/modals/CourseSettingsModal.tsx
(5 hunks)services/course-material/src/services/backend.ts
(3 hunks)services/headless-lms/models/src/marketing_consents.rs
(1 hunks)services/headless-lms/server/src/controllers/course_material/courses.rs
(3 hunks)services/headless-lms/server/src/programs/mailchimp_syncer.rs
(1 hunks)services/headless-lms/server/src/ts_binding_generator.rs
(1 hunks)shared-module/packages/common/src/bindings.guard.ts
(6 hunks)shared-module/packages/common/src/bindings.ts
(5 hunks)shared-module/packages/common/src/locales/en/course-material.json
(1 hunks)shared-module/packages/common/src/locales/uk/course-material.json
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- services/headless-lms/server/src/ts_binding_generator.rs
🔇 Additional comments (13)
services/headless-lms/models/src/marketing_consents.rs (2)
226-229
: Fix potential misalignment in vectors due to separate filter_map
usage
In update_user_mailchimp_id_at_to_all_synced_users
, parsing user_ids
separately using filter_map
can result in vectors of differing lengths between user_ids
and user_mailchimp_ids
. This may lead to misaligned data in the SQL update query. Consider validating all fields together and collecting only fully valid entries.
260-277
: Fix potential misalignment in vectors due to separate filter_map
usage
In update_unsubscribed_users_from_mailchimp_in_bulk
, parsing user_ids
, timestamps
, and course_language_group_ids
separately using filter_map
can result in vectors of differing lengths. This could cause incorrect associations in the SQL update query. Consider parsing and validating all fields together, collecting only entries where all fields are successfully parsed.
shared-module/packages/common/src/locales/uk/course-material.json (1)
128-129
: Verify Ukrainian translations with native speakers.
The marketing consent and privacy policy translations appear well-structured. However, it's recommended to have them reviewed by native Ukrainian speakers to ensure accuracy and cultural appropriateness.
✅ Verification successful
Ukrainian translations appear accurate and consistent with English source
The Ukrainian translations closely match the English source text in both meaning and structure:
- Marketing consent text properly conveys the same information about updates, contact sharing, and third-party platforms
- Privacy policy acceptance text accurately translates the terms acceptance statement
- Text lengths are comparable (EN: 239 chars, UK: 248 chars) which is reasonable given language differences
The translations are only present in course-material.json files for both languages, maintaining consistency across the codebase.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for consistency across language files
echo "Checking for marketing consent keys in other language files..."
fd -e json -e yaml . shared-module/packages/common/src/locales \
-x grep -l "marketing-consent"
echo -e "\nComparing length of translations..."
for file in $(fd -e json . shared-module/packages/common/src/locales); do
echo "=== $file ==="
jq -r '."marketing-consent-checkbox-text" // empty | length' "$file"
done
Length of output: 10245
Script:
#!/bin/bash
# Let's check the actual content of marketing consent keys in English and Ukrainian files
echo "=== English marketing consent text (course-material.json) ==="
jq -r '."marketing-consent-checkbox-text", ."marketing-consent-privacy-policy-checkbox-text"' shared-module/packages/common/src/locales/en/course-material.json
echo -e "\n=== Ukrainian marketing consent text (course-material.json) ==="
jq -r '."marketing-consent-checkbox-text", ."marketing-consent-privacy-policy-checkbox-text"' shared-module/packages/common/src/locales/uk/course-material.json
# Check if these keys exist in main-frontend.json files as well
echo -e "\n=== English marketing consent text (main-frontend.json) ==="
jq -r '."marketing-consent-checkbox-text", ."marketing-consent-privacy-policy-checkbox-text"' shared-module/packages/common/src/locales/en/main-frontend.json
echo -e "\n=== Ukrainian marketing consent text (main-frontend.json) ==="
jq -r '."marketing-consent-checkbox-text", ."marketing-consent-privacy-policy-checkbox-text"' shared-module/packages/common/src/locales/uk/main-frontend.json
Length of output: 1813
services/headless-lms/server/src/programs/mailchimp_syncer.rs (3)
26-72
: LGTM! Well-structured field definitions.
The required fields are clearly defined with appropriate default values. The schema is comprehensive and includes all necessary user data fields.
694-698
:
Fix formatting in error logging.
The error message in eprintln!
does not correctly format the chunk number and the error.
- error!(
- "Error while processing chunk {}/{}: ",
- (total_records + BATCH_SIZE - 1) / BATCH_SIZE,
- e
- );
+ error!(
+ "Error while processing chunk {}/{}: {}",
+ (total_records + BATCH_SIZE - 1) / BATCH_SIZE,
+ total_records / BATCH_SIZE,
+ e
+ );
Likely invalid or redundant comment.
179-187
: 🛠️ Refactor suggestion
Implement retry mechanism for field operations.
The current implementation only logs warnings for field removal failures. Consider implementing a retry mechanism with exponential backoff for transient failures.
- if let Err(e) =
- remove_field_from_mailchimp(list_id, &field.field_id, server_prefix, access_token)
- .await
- {
- warn!("Could not remove field '{}': {}", field.field_name, e);
+ let mut retries = 3;
+ let mut delay = Duration::from_secs(1);
+ while retries > 0 {
+ match remove_field_from_mailchimp(list_id, &field.field_id, server_prefix, access_token).await {
+ Ok(_) => break,
+ Err(e) if retries > 1 => {
+ warn!("Failed to remove field '{}', retrying in {:?}: {}", field.field_name, delay, e);
+ tokio::time::sleep(delay).await;
+ delay *= 2;
+ retries -= 1;
+ }
+ Err(e) => {
+ error!("Failed to remove field '{}' after all retries: {}", field.field_name, e);
+ }
+ }
Likely invalid or redundant comment.
services/headless-lms/server/src/controllers/course_material/courses.rs (3)
905-911
: LGTM! Well-structured payload for marketing consent
The struct has appropriate field types and clear naming that matches its purpose.
947-967
: LGTM! Well-implemented consent fetching
The function properly handles authorization and optional results.
1050-1057
: LGTM! Routes follow API conventions
The routes are properly configured with appropriate HTTP methods and consistent URL patterns.
shared-module/packages/common/src/bindings.ts (2)
1156-1168
: LGTM! Well-structured interface for marketing consent
The interface has comprehensive fields with proper typing, including audit fields and Mailchimp integration fields.
478-478
: LGTM! Consistent interface updates
The ask_marketing_consent
field is consistently added to all relevant interfaces with proper typing.
Also applies to: 508-508, 525-525
shared-module/packages/common/src/bindings.guard.ts (2)
2199-2217
: LGTM! Comprehensive type guard implementation
The type guard properly validates all fields with appropriate type checks and null handling.
1028-1029
: LGTM! Consistent type guard updates
The type guards are consistently updated to validate the new ask_marketing_consent
field.
Also applies to: 1077-1078, 1098-1099
const getCourse = useQuery({ | ||
queryKey: ["courses", selectedLangCourseId], | ||
queryFn: () => fetchCourseById(selectedLangCourseId as NonNullable<string>), | ||
enabled: selectedLangCourseId !== null, | ||
}) |
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.
Correct the 'enabled' condition in the getCourse
query
The enabled
condition checks selectedLangCourseId !== null
, but since selectedLangCourseId
is of type string
, it cannot be null
. Consider removing the enabled
condition or updating it to check for undefined
if selectedLangCourseId
can be undefined
.
if (getCourse.isSuccess) { | ||
await updateMarketingConsent( | ||
getCourse.data.id, | ||
getCourse.data.course_language_group_id, | ||
isEmailSubscriptionConsentChecked, | ||
isMarketingConsentChecked, | ||
) | ||
} |
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.
Add error handling for getCourse
query failure
Currently, updateMarketingConsent
is called only if getCourse.isSuccess
is true, but there's no error handling for when getCourse
fails. Consider handling the failure case to inform the user or retry the fetch operation.
disabled={ | ||
!selectedInstanceId || | ||
additionalQuestionsQuery.isPending || | ||
(getCourse.data?.ask_marketing_consent && !isEmailSubscriptionConsentChecked) | ||
} |
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.
Ensure both consents are required before enabling the 'Continue' button
The disabled
condition for the 'Continue' button only checks isEmailSubscriptionConsentChecked
. If both marketing consent and email subscription consent are required, consider updating the condition to also check isMarketingConsentChecked
to prevent users from proceeding without providing both consents.
const askMarketingConsent = useQuery({ | ||
queryKey: ["courses", selectedLangCourseId], | ||
queryFn: () => fetchCourseById(selectedLangCourseId as NonNullable<string>), | ||
enabled: selectedLangCourseId !== null, | ||
}).data?.ask_marketing_consent | ||
|
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.
Handle loading and error states for askMarketingConsent
The askMarketingConsent
value is directly accessed without handling loading or error states from the getCourse
query. This might lead to unexpected behavior if the data is not yet available or if an error occurs. Consider checking getCourse.isLoading
and getCourse.isError
before using getCourse.data
.
setOpen( | ||
(signedIn && shouldChooseInstance) || | ||
(signedIn && manualOpen) || | ||
(signedIn && askMarketingConsent === true && checkUserMarketingConsent === "unsubscribed"), | ||
) | ||
}, [loginState, pageState, manualOpen, askMarketingConsent, checkUserMarketingConsent]) |
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.
Improve the condition for opening the modal
The condition for setting open
combines several states but may not account for loading or error states of the queries. Consider adding checks for getCourse.isSuccess
and checkUserMarketingConsent
to ensure the modal only opens when the data is available and reliable.
/** | ||
POST `/api/v0/course-material/courses/:course_id/user-marketing-consent` - Adds or updates user's marketing consent for a specific course. | ||
*/ | ||
#[instrument(skip(pool, payload))] | ||
async fn update_marketing_consent( | ||
payload: web::Json<UserMarketingConsentPayload>, | ||
pool: web::Data<PgPool>, | ||
course_id: web::Path<Uuid>, | ||
user: AuthUser, | ||
) -> ControllerResult<web::Json<Uuid>> { | ||
let mut conn = pool.acquire().await?; | ||
let user_id = Some(user.id); | ||
|
||
let token = authorize_access_to_course_material(&mut conn, user_id, *course_id).await?; | ||
|
||
let email_subscription = if payload.email_subscription { | ||
"subscribed" | ||
} else { | ||
"unsubscribed" | ||
}; | ||
|
||
let result = models::marketing_consents::upsert_marketing_consent( | ||
&mut conn, | ||
*course_id, | ||
payload.course_language_groups_id, | ||
&user.id, | ||
email_subscription, | ||
payload.marketing_consent, | ||
) | ||
.await?; | ||
|
||
token.authorized_ok(web::Json(result)) | ||
} |
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.
🛠️ Refactor suggestion
Add validation for course_language_groups_id
The function should validate that the provided course_language_groups_id
belongs to the specified course to prevent potential data inconsistencies.
// Add this validation before line 934
let valid = sqlx::query!(
r#"
SELECT 1 FROM courses
WHERE id = $1 AND course_language_group_id = $2
"#,
course_id,
payload.course_language_groups_id
)
.fetch_optional(&mut conn)
.await?;
if valid.is_none() {
return Err(Error::BadRequest("Invalid course_language_groups_id for this course".into()));
}
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.
Actionable comments posted: 4
🧹 Outside diff range and nitpick comments (5)
services/course-material/src/components/ContentRenderer/core/formatting/TableBlock.tsx (1)
86-86
: LGTM! Consider adding parentheses for better readability.The content rendering logic is correct and maintains the same behavior. While the parentheses removal doesn't affect functionality, adding them back could improve readability as suggested by the linter:
-cell.content !== "" ? cell.content ?? "" : "" +(cell.content !== "" ? cell.content ?? "" : "")Also applies to: 108-108, 130-130
🧰 Tools
🪛 eslint
[error] 86-86: Replace
cell.content·??·""
with(cell.content·??·"")
(prettier/prettier)
services/headless-lms/models/.sqlx/query-bbec51d086e46c8b3e4e547fc88ad8ed5ddd43db737ec99e48391668646d49f9.json (1)
Consider adding an index to optimize query performance
Based on the migration file analysis, while there is a unique constraint on (user_id, course_language_group_id), there's no index that fully supports the WHERE clause conditions in the batch update query. Adding the following index would improve query performance:
CREATE INDEX idx_marketing_consents_sync ON user_marketing_consents(user_id, consent, synced_to_mailchimp_at, course_language_group_id);
- File:
services/headless-lms/migrations/
- Create a new migration to add the suggested indexThe other concerns about transaction isolation and batch size should be handled at the application level, as they are not directly related to the SQL query structure.
🔗 Analysis chain
Line range hint
3-16
: Consider performance and consistency implicationsThe batch update query has several potential issues:
- Large arrays in UNNEST could impact performance
- No explicit transaction isolation level
- Possible race conditions in timestamp comparisons
Recommendations:
- Add batch size limits in the application code
- Consider using
SERIALIZABLE
isolation level- Add appropriate indexes:
CREATE INDEX idx_marketing_consents_sync ON user_marketing_consents(user_id, consent, synced_to_mailchimp_at, course_language_group_id);🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Check for existing indexes on user_marketing_consents table rg -A 10 "CREATE.*INDEX.*ON.*user_marketing_consents"Length of output: 55
Script:
#!/bin/bash # Let's first check for migration files that might contain index definitions fd -e sql | xargs rg -l "user_marketing_consents" # Then check for table creation to understand the schema fd -e sql | xargs rg -A 10 "CREATE TABLE.*user_marketing_consents"Length of output: 1659
Script:
#!/bin/bash # Let's check the complete migration file for any indexes cat services/headless-lms/migrations/20241023104801_add-marketing-consent.up.sql # Also check if there are any subsequent migrations that might have added indexes fd -e sql -e up.sql -d services/headless-lms/migrations | sort | grep -A 999 20241023104801Length of output: 4935
services/headless-lms/models/.sqlx/query-f52994d7e2e279a1a5f31d4486f6911035d1d91ddcb9d346df8aa848c0b29ec7.json (1)
1-122
: Consider adding indexes for performance optimizationThe query involves multiple LEFT JOINs and complex conditions. Consider adding indexes on frequently joined columns to improve query performance:
user_marketing_consents(course_language_group_id)
course_module_completions(user_id, course_id)
course_specific_consent_form_answers(course_id, user_id)
user_research_consents(user_id)
services/headless-lms/server/src/controllers/course_material/courses.rs (2)
906-912
: Consider adding validation for UserMarketingConsentPayloadThe struct should include validation to ensure the fields meet any business requirements (e.g., email format validation if needed).
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Validate)] #[cfg_attr(feature = "ts_rs", derive(TS))] pub struct UserMarketingConsentPayload { #[validate(custom = "validate_course_language_group")] pub course_language_groups_id: Uuid, pub email_subscription: bool, pub marketing_consent: bool, }
929-934
: Consider using an enum for email subscription statusInstead of using string literals, consider using an enum for better type safety and maintainability.
#[derive(Debug, Serialize, Deserialize)] pub enum EmailSubscriptionStatus { Subscribed, Unsubscribed, } let email_subscription = if payload.email_subscription { EmailSubscriptionStatus::Subscribed } else { EmailSubscriptionStatus::Unsubscribed };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (14)
services/course-material/src/components/ContentRenderer/core/formatting/TableBlock.tsx
(3 hunks)services/course-material/src/services/backend.ts
(3 hunks)services/headless-lms/models/.sqlx/query-3036b60a162754931a2128c92caf88b9e98e50393c53dc95ac919e7065eb4a65.json
(1 hunks)services/headless-lms/models/.sqlx/query-6f9c9d6f61094b88673acaa9d820bc13fd0bbc0f7d52c0e3714ba77e0c81280e.json
(1 hunks)services/headless-lms/models/.sqlx/query-bbec51d086e46c8b3e4e547fc88ad8ed5ddd43db737ec99e48391668646d49f9.json
(1 hunks)services/headless-lms/models/.sqlx/query-f52994d7e2e279a1a5f31d4486f6911035d1d91ddcb9d346df8aa848c0b29ec7.json
(1 hunks)services/headless-lms/models/src/lib.rs
(1 hunks)services/headless-lms/server/src/controllers/course_material/courses.rs
(3 hunks)services/headless-lms/server/src/ts_binding_generator.rs
(1 hunks)shared-module/packages/common/src/bindings.guard.ts
(5 hunks)shared-module/packages/common/src/bindings.ts
(4 hunks)shared-module/packages/common/src/locales/en/main-frontend.json
(1 hunks)shared-module/packages/common/src/locales/fi/main-frontend.json
(1 hunks)shared-module/packages/common/src/locales/uk/course-material.json
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- shared-module/packages/common/src/locales/en/main-frontend.json
- services/headless-lms/models/src/lib.rs
- shared-module/packages/common/src/locales/fi/main-frontend.json
- services/headless-lms/server/src/ts_binding_generator.rs
- shared-module/packages/common/src/locales/uk/course-material.json
🧰 Additional context used
🪛 eslint
services/course-material/src/components/ContentRenderer/core/formatting/TableBlock.tsx
[error] 86-86: Replace cell.content·??·""
with (cell.content·??·"")
(prettier/prettier)
[error] 108-108: Replace cell.content·??·""
with (cell.content·??·"")
(prettier/prettier)
[error] 130-130: Replace cell.content·??·""
with (cell.content·??·"")
(prettier/prettier)
🔇 Additional comments (14)
services/course-material/src/components/ContentRenderer/core/formatting/TableBlock.tsx (2)
Line range hint 1-152
: Clarification needed: Marketing consent integration
While the AI summary suggests this component's involvement in marketing consent functionality, this appears to be a generic table renderer with no direct handling of marketing consent data. Please clarify if this component needs to be modified to support marketing consent features.
🧰 Tools
🪛 Biome (1.9.4)
[error] 84-84: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🪛 eslint
[error] 86-86: Replace cell.content·??·""
with (cell.content·??·"")
(prettier/prettier)
86-89
: Verify XSS protection in parseText utility
The component uses dangerouslySetInnerHTML with parseText sanitization. While this appears safe, we should verify that parseText properly sanitizes HTML content.
Also applies to: 108-111, 130-133
✅ Verification successful
XSS protection is properly implemented
The code uses DOMPurify for HTML sanitization, which is a robust and well-maintained XSS sanitization library. The parseText
utility properly sanitizes content by:
- Processing LaTeX, citations, and glossary terms
- Always applying DOMPurify sanitization as the final step before returning content
- Using trusted types configuration for additional security
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for parseText implementation to verify sanitization
ast-grep --pattern 'function parseText($_) {
$$$
}'
Length of output: 55
Script:
#!/bin/bash
# Let's try different approaches to find the parseText implementation
# First, let's search for parseText using ripgrep to see its usage and definition
rg -l "parseText"
# Then let's get the actual content of files containing parseText
rg "parseText" -A 5 -B 5
# Also search for any sanitization related imports or utilities
rg -l "sanitize|xss|DOMPurify|escape"
Length of output: 48106
Script:
#!/bin/bash
# Let's check the sanitization implementation in the course material
cat services/course-material/src/utils/sanitizeCourseMaterialHtml.ts
# Let's also check the parseText implementation
cat services/course-material/src/components/ContentRenderer/util/textParsing.tsx
Length of output: 3394
🧰 Tools
🪛 eslint
[error] 86-86: Replace cell.content·??·""
with (cell.content·??·"")
(prettier/prettier)
services/course-material/src/services/backend.ts (2)
751-769
: Fix the API endpoint path and improve type safety
- The API endpoint path is missing a leading slash
- Consider using a more specific type for the response instead of string
771-776
: Fix the API endpoint path
The API endpoint path is missing a leading slash
services/headless-lms/server/src/controllers/course_material/courses.rs (2)
935-944
: Add validation for course_language_groups_id
The function should validate that the provided course_language_groups_id
belongs to the specified course to prevent potential data inconsistencies.
1072-1079
: LGTM: Route configuration is properly implemented
The new routes are correctly added to the configuration with proper HTTP methods and handlers.
shared-module/packages/common/src/bindings.ts (4)
478-478
: LGTM! New marketing consent field added to Course interface
The boolean field ask_marketing_consent
is properly typed and follows naming conventions.
508-508
: LGTM! Marketing consent field added to CourseUpdate interface
The boolean field matches the Course interface definition.
525-525
: LGTM! Marketing consent field added to NewCourse interface
The boolean field maintains consistency with Course and CourseUpdate interfaces.
1156-1168
: LGTM! Well-structured UserMarketingConsent interface
The interface is well-designed with:
- Proper tracking fields (created_at, updated_at, deleted_at)
- Mailchimp integration fields
- Appropriate use of nullable types
shared-module/packages/common/src/bindings.guard.ts (4)
1030-1031
: LGTM! Course type guard updated for marketing consent
The type guard properly validates the new boolean field.
1079-1080
: LGTM! CourseUpdate type guard updated for marketing consent
The type guard maintains consistency with Course type guard.
1100-1101
: LGTM! NewCourse type guard updated for marketing consent
The type guard maintains consistency with related type guards.
2201-2219
: LGTM! Comprehensive type guard for UserMarketingConsent
The type guard properly validates:
- All required fields with correct types
- Nullable fields with appropriate checks
- Follows established type guard patterns in the codebase
"db_name": "PostgreSQL", | ||
"query": "\n INSERT INTO user_marketing_consents (user_id, course_id, course_language_group_id, consent, email_subscription_in_mailchimp)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (user_id, course_language_group_id)\n DO UPDATE\n SET\n consent = $4,\n email_subscription_in_mailchimp = $5\n RETURNING id\n ", |
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.
🛠️ Refactor suggestion
Add timestamps for better tracking and auditing
The query should include created_at
and updated_at
timestamps to track when consents are created or modified. This is especially important for audit trails and synchronization with external services like Mailchimp.
"query": "\n INSERT INTO user_marketing_consents (user_id, course_id, course_language_group_id, consent, email_subscription_in_mailchimp)\n VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (user_id, course_language_group_id)\n DO UPDATE\n SET\n consent = $4,
- email_subscription_in_mailchimp = $5\n RETURNING id\n ",
+ email_subscription_in_mailchimp = $5,
+ updated_at = CURRENT_TIMESTAMP\n RETURNING id\n ",
Committable suggestion skipped: line range outside the PR's diff.
"Left": ["Uuid", "Uuid", "Uuid", "Bool", "Varchar"] | ||
}, |
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.
💡 Codebase verification
Consider using ENUM type for email_subscription_in_mailchimp
Based on the code analysis, the email_subscription_in_mailchimp
column has specific expected values used in the Mailchimp syncer: "subscribed", "unsubscribed", and "non-subscribed". To ensure data consistency, you should consider using an ENUM type instead of VARCHAR.
Example SQL modification:
CREATE TYPE email_subscription_status AS ENUM ('subscribed', 'unsubscribed', 'non-subscribed');
ALTER TABLE user_marketing_consents
ALTER COLUMN email_subscription_in_mailchimp TYPE email_subscription_status
USING email_subscription_in_mailchimp::email_subscription_status;
🔗 Analysis chain
Validate email_subscription_in_mailchimp values
The email_subscription_in_mailchimp
parameter is defined as Varchar
without any constraints. Consider adding a CHECK constraint or using an ENUM type to restrict possible values.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if there's a constraint on email_subscription_in_mailchimp column
rg -A 5 "email_subscription_in_mailchimp.*CHECK|CREATE.*TYPE.*email_subscription"
Length of output: 83
Script:
#!/bin/bash
# Let's first check the schema definition for this column
rg -l "email_subscription_in_mailchimp"
# Then let's look at the actual SQL migrations or schema files
fd -e sql | xargs rg "email_subscription_in_mailchimp"
Length of output: 871
Script:
#!/bin/bash
# Let's check the Rust model and any potential validations
cat services/headless-lms/models/src/marketing_consents.rs
# Let's also check if there are any enum definitions in TypeScript that might indicate expected values
cat shared-module/packages/common/src/bindings.ts
Length of output: 61884
Script:
#!/bin/bash
# Let's check the mailchimp syncer to understand the expected values
cat services/headless-lms/server/src/programs/mailchimp_syncer.rs
Length of output: 22807
"parameters": { | ||
"Left": ["Uuid"] | ||
}, |
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.
🛠️ Refactor suggestion
Update parameters for pagination
Add LIMIT and OFFSET parameters to support pagination:
"parameters": {
- "Left": ["Uuid"]
+ "Left": ["Uuid", "Int8", "Int8"]
},
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
"parameters": { | |
"Left": ["Uuid"] | |
}, | |
"parameters": { | |
"Left": ["Uuid", "Int8", "Int8"] | |
}, |
"query": "\n SELECT\n umc.user_id,\n u.email AS email,\n umc.email_subscription_in_mailchimp,\n umc.user_mailchimp_id\n FROM user_marketing_consents AS umc\n JOIN user_details AS u ON u.user_id = umc.user_id\n WHERE umc.course_language_group_id = $1\n AND umc.synced_to_mailchimp_at < u.updated_at\n ", | ||
"describe": { | ||
"columns": [ | ||
{ | ||
"ordinal": 0, | ||
"name": "user_id", | ||
"type_info": "Uuid" | ||
}, |
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.
💡 Codebase verification
Based on the migration files examined, I can now generate the final response:
Consider adding index for synced_to_mailchimp_at and updated_at comparison
The query could benefit from an index to optimize the JOIN and WHERE conditions. The table definitions show:
user_details
has a unique index on emailuser_marketing_consents
has a unique constraint on (user_id, course_language_group_id)- No index exists for the timestamp comparison
synced_to_mailchimp_at < u.updated_at
Consider adding a composite index:
CREATE INDEX idx_marketing_consent_sync_status
ON user_marketing_consents (course_language_group_id, synced_to_mailchimp_at)
WHERE deleted_at IS NULL;
• The email exposure is acceptable as this query appears to be part of a Mailchimp integration system where email addresses are required for synchronization.
• Pagination should be considered if large result sets are expected.
🔗 Analysis chain
Add pagination and review data exposure
The query retrieves sensitive user data (email addresses) and could return large result sets. Consider:
- Adding pagination using LIMIT/OFFSET
- Reviewing if email addresses need to be exposed in this query
- Adding appropriate indexes for the JOIN condition
"query": "\n SELECT\n umc.user_id,\n u.email AS email,\n umc.email_subscription_in_mailchimp,\n umc.user_mailchimp_id\n FROM user_marketing_consents AS umc\n JOIN user_details AS u ON u.user_id = umc.user_id\n WHERE umc.course_language_group_id = $1\n AND umc.synced_to_mailchimp_at < u.updated_at\n
+ LIMIT $2 OFFSET $3\n ",
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for existing indexes on the JOIN columns
rg -A 5 "CREATE.*INDEX.*ON.*(user_marketing_consents|user_details).*user_id"
# Look for other queries that might expose email addresses
rg "SELECT.*email.*FROM.*user_details"
Length of output: 119
Script:
#!/bin/bash
# Let's try a different approach to find schema definitions and indexes
# First, look for migration files
fd -e sql
# Then check their content for table definitions and indexes
fd -e sql -x cat {}
Length of output: 65984
Script:
#!/bin/bash
# Let's check for user_marketing_consents table definition and indexes
fd -e sql -x grep -l "user_marketing_consents" {}
# Then examine the specific migration file content
fd -e sql -x grep -A 20 "CREATE TABLE user_marketing_consents" {}
# Also check for user_details table definition
fd -e sql -x grep -A 20 "CREATE TABLE user_details" {}
Length of output: 2918
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.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (2)
kubernetes/base/headless-lms/mailchimp-syncer.yml (1)
25-31
: Consider increasing resource limits for the syncing service.The current resource limits might be too restrictive for a service that handles bulk operations:
- Memory limit of 300Mi might be insufficient for large batches
- CPU limit of 200m could throttle the service during peak operations
resources: requests: memory: 100Mi cpu: 20m limits: - memory: 300Mi - cpu: 200m + memory: 512Mi + cpu: 500mservices/headless-lms/server/src/controllers/course_material/courses.rs (1)
929-933
: Consider moving email subscription string conversion to model layerThe conversion of boolean to string should ideally be handled in the model layer (
models::marketing_consents
) to maintain better separation of concerns.- let email_subscription = if payload.email_subscription { - "subscribed" - } else { - "unsubscribed" - }; let result = models::marketing_consents::upsert_marketing_consent( &mut conn, *course_id, payload.course_language_groups_id, &user.id, - email_subscription, + payload.email_subscription, payload.marketing_consent, )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (10)
kubernetes/base/headless-lms/mailchimp-syncer.yml
(1 hunks)kubernetes/base/kustomization.yaml
(1 hunks)services/course-material/src/services/backend.ts
(3 hunks)services/headless-lms/models/src/lib.rs
(1 hunks)services/headless-lms/server/src/controllers/course_material/courses.rs
(3 hunks)services/headless-lms/server/src/programs/mailchimp_syncer.rs
(1 hunks)services/headless-lms/server/src/ts_binding_generator.rs
(1 hunks)shared-module/packages/common/src/bindings.guard.ts
(5 hunks)shared-module/packages/common/src/bindings.ts
(4 hunks)shared-module/packages/common/src/locales/en/course-material.json
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- services/headless-lms/models/src/lib.rs
- services/headless-lms/server/src/ts_binding_generator.rs
- shared-module/packages/common/src/locales/en/course-material.json
- shared-module/packages/common/src/bindings.ts
- services/course-material/src/services/backend.ts
🔇 Additional comments (13)
shared-module/packages/common/src/bindings.guard.ts (4)
2202-2220
: LGTM! The isUserMarketingConsent type guard is well implemented.
The type guard correctly validates all required properties of the UserMarketingConsent interface and properly handles optional properties with null checks. The implementation follows the established pattern in the codebase.
1031-1032
: LGTM! The isCourse type guard updates are correct.
The changes properly validate the new optional join_code and required ask_marketing_consent properties with appropriate type checks.
1080-1081
: LGTM! The isCourseUpdate type guard updates are correct.
The changes properly validate the new required is_joinable_by_code_only and ask_marketing_consent properties with boolean type checks.
1101-1102
: LGTM! The isNewCourse type guard updates are correct.
The changes properly validate the new optional join_code and required ask_marketing_consent properties with appropriate type checks.
kubernetes/base/kustomization.yaml (1)
21-21
: LGTM!
The addition of the mailchimp-syncer resource follows the existing pattern in the kustomization file.
services/headless-lms/server/src/programs/mailchimp_syncer.rs (5)
350-453
: Refactor sync_contacts for better separation of concerns.
The function handles multiple responsibilities: fetching tokens, processing unsubscribed users, updating emails, and syncing consents.
701-705
: Fix formatting in error logging.
The error message in eprintln!
does not correctly format the chunk number and the error.
78-79
: 🛠️ Refactor suggestion
Consider implementing rate limiting and increasing sync interval.
A 10-second sync interval might be too aggressive and could:
- Hit Mailchimp API rate limits
- Create unnecessary load on both systems
- Increase costs due to frequent API calls
Consider:
- Increasing
SYNC_INTERVAL_SECS
to a more conservative value (e.g., 300 seconds) - Implementing rate limiting for API calls
189-190
: 🛠️ Refactor suggestion
Implement retry mechanism for field operations.
Field addition/removal failures are only logged as warnings without retry attempts. This could lead to incomplete schema setup.
Also applies to: 206-209
240-240
:
Inconsistent authentication method with Mailchimp API.
The Mailchimp API expects the Authorization
header to include apikey
followed by the access token.
Also applies to: 295-295, 330-330, 512-512, 588-588, 631-631
services/headless-lms/server/src/controllers/course_material/courses.rs (3)
906-912
: LGTM: Well-structured payload definition
The struct is well-defined with appropriate field types and necessary derive macros for serialization/deserialization.
952-968
: LGTM: Well-implemented fetch endpoint
The function properly handles authorization, error cases, and returns an appropriate optional type for the consent data.
1091-1098
: LGTM: Routes properly configured
The new routes follow RESTful conventions and are consistently integrated with existing course-related endpoints.
echo Waiting for postgres to be available | ||
timeout 120 ./wait-for-db.sh | ||
./wait-for-db-migrations.sh |
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.
🛠️ Refactor suggestion
Review database wait timeout and add health checks.
Two potential improvements:
- The 120-second timeout for database readiness might be insufficient for large databases
- Missing liveness and readiness probes which are crucial for Kubernetes health monitoring
Add health check probes:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
let result = models::marketing_consents::upsert_marketing_consent( | ||
&mut conn, | ||
*course_id, | ||
payload.course_language_groups_id, | ||
&user.id, | ||
email_subscription, | ||
payload.marketing_consent, | ||
) | ||
.await?; |
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.
Add validation for course_language_groups_id
Add validation to ensure the provided course_language_groups_id
belongs to the specified course to prevent potential data inconsistencies.
Add this validation before the upsert:
let valid = sqlx::query!(
r#"
SELECT 1 FROM courses
WHERE id = $1 AND course_language_group_id = $2
"#,
*course_id,
payload.course_language_groups_id
)
.fetch_optional(&mut conn)
.await?;
if valid.is_none() {
return Err(Error::BadRequest("Invalid course_language_groups_id for this course".into()));
}
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.
// eslint-disable-next-line playwright/no-wait-for-timeout | ||
await page.waitForTimeout(200) |
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.
🛠️ Refactor suggestion
Replace arbitrary timeout with proper wait condition
The addition of waitForTimeout
is a fragile solution that could lead to flaky tests. Playwright provides built-in synchronization mechanisms that are more reliable.
Consider replacing the timeout with a proper wait condition:
- // eslint-disable-next-line playwright/no-wait-for-timeout
- await page.waitForTimeout(200)
+ // Wait for any animations or state changes to complete
+ await page.waitForLoadState('networkidle')
+ // Or wait for a specific element state change that indicates readiness
+ await page.getByRole("button", { name: "Continue" }).waitFor({ state: "stable" })
This approach:
- Follows Playwright's best practices
- Makes the test more reliable by waiting for actual conditions rather than arbitrary timeouts
- Removes the need for eslint disable comment
Committable suggestion skipped: line range outside the PR's diff.
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.
// eslint-disable-next-line playwright/no-wait-for-timeout | ||
await page.waitForTimeout(200) |
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.
🛠️ Refactor suggestion
Replace explicit timeout with a proper wait condition.
Using waitForTimeout
is an anti-pattern in E2E testing as it makes tests brittle and can mask underlying issues. Consider using Playwright's built-in waiting mechanisms instead.
Here's a more robust approach:
- // eslint-disable-next-line playwright/no-wait-for-timeout
- await page.waitForTimeout(200)
+ // Wait for any animations or state changes to complete
+ await page.waitForLoadState('networkidle')
+ // Or wait for a specific element that indicates the action is complete
+ await page.getByRole("button", { name: "Continue" }).waitFor({ state: "stable" })
This change will:
- Make the test more reliable across different environments
- Better handle varying network conditions
- Follow Playwright's best practices for waiting
Committable suggestion skipped: line range outside the PR's diff.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation
Tests
Chores