Skip to content

Template migrations #778

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

Closed
wants to merge 7 commits into from

Conversation

BilalG1
Copy link
Contributor

@BilalG1 BilalG1 commented Jul 18, 2025


Important

This PR adds functionality for migrating email templates and themes, including backend API endpoints and frontend UI components for managing the migration process.

  • Backend API:
    • Adds POST and GET handlers in route.tsx for email template conversion, handling project-specific template migration.
    • Implements convertHandlebarsToJSX and generateTsxSourceFromConfiguration in convert.tsx for transforming email templates.
    • Updates route.tsx in email-themes to manage email themes, ensuring active theme consistency.
  • Frontend Components:
    • Introduces page-client.tsx for managing template migration UI, including project ID retrieval and migration execution.
    • Adds page.tsx to render the PageClient component.
  • Admin Interface:
    • Extends admin-interface.ts with methods for fetching project IDs and converting email templates.
    • Updates admin-app-impl.ts and admin-app.ts to include new migration methods and cache management.

This description was created by Ellipsis for 871eda9. You can customize this summary. It will automatically update as commits are pushed.

Copy link

vercel bot commented Jul 18, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
stack-backend ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 18, 2025 1:23am
stack-dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 18, 2025 1:23am
stack-demo ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 18, 2025 1:23am
stack-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 18, 2025 1:23am

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Summary

This PR implements a comprehensive email template migration system, transitioning from Handlebars-based templates to React-based components using @react-email/components. The changes include:

  1. A new migration utility that converts Handlebars syntax to JSX
  2. Internal API endpoints for template conversion
  3. UI components for administrators to monitor and control the migration process
  4. Enhanced type safety improvements across the codebase
  5. Fallback mechanisms for email themes to ensure system stability during migration

The implementation allows for a controlled, project-by-project migration approach with proper error handling and progress tracking. The code is well-structured with clear separation between the migration utility, API endpoints, and UI components.

Confidence score: 4/5

  1. This PR is generally safe to merge as it includes proper error handling and fallback mechanisms
  2. The high score is due to comprehensive test coverage and careful handling of edge cases, only slightly reduced due to the complexity of template conversion
  3. Key files needing attention:
    • apps/backend/src/app/api/latest/internal/email-templates/temp/convert.tsx - Core conversion logic
    • apps/backend/src/app/api/latest/internal/email-themes/route.tsx - Theme fallback mechanism

10 files reviewed, 10 comments
Edit PR Review Bot Settings | Greptile

Comment on lines +31 to +38
for (const projectId of projectIds) {
const result = await adminApp.convertEmailTemplates(projectId);
setMigrationResults(prev => [...prev, {
projectId,
templatesConverted: result.templates_converted,
totalTemplates: result.total_templates
}]);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Migration is sequential and could take a long time for many projects. Consider using Promise.all() or batching for parallel processing


export function getTransformedTemplateMetadata(metadata: EmailTemplateMetadata) {
const variableNames = metadata.variables.map(v => v.name);
const configuration = metadata.defaultContent[2];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Accessing defaultContent[2] directly is fragile. Add type checking or document the expected structure

Comment on lines +39 to +41
} catch (error) {
console.error("Error running migration:", error);
} finally {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Error handling is too generic. Need to handle failed migrations individually and provide retry mechanism

@@ -67,10 +67,10 @@ export function createHistoryAdapter(
return {
async load() {
const { messages } = await adminApp.listChatMessages(threadId);
return { messages } as ExportedMessageRepository;
return { messages } as unknown as ExportedMessageRepository;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Double type assertion (as unknown as) could mask potential type mismatches. Consider creating a proper type conversion function.

Comment on lines +278 to +280
${indent} }}
${indent} target="_blank"
${indent} >
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: target="_blank" links should include rel="noopener" for security

Suggested change
${indent} }}
${indent} target="_blank"
${indent} >
${indent} }}
${indent} target="_blank"
${indent} rel="noopener"
${indent} >

const ids = await adminApp.getProjectIdsForTemplatesMigration();
setProjectIds(ids);
} catch (error) {
console.error("Error getting project IDs:", error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Error not displayed to user. Add error toast/alert for better UX

if (DEFAULT_EMAIL_THEME_ID in themeList) {
newActiveTheme = DEFAULT_EMAIL_THEME_ID;
} else {
newActiveTheme = Object.keys(themeList)[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: consider checking if themeList is empty before accessing index 0

},
async append(message) {
await adminApp.saveChatMessage(threadId, message);
await adminApp.saveChatMessage(threadId, message as unknown as ChatMessage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Use type guard or validation before casting to ChatMessage to ensure message has required structure

const templateMetadata = {
...defaultTemplateMetadata,
defaultContent: {
[2]: template.content,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Magic number '2' used for content version - consider defining this as a constant with documentation on its significance

[2]: template.content,
},
};
emailTemplates[configTemplateId] = getTransformedTemplateMetadata(templateMetadata as unknown as EmailTemplateMetadata);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Type assertion to unknown then EmailTemplateMetadata suggests potential type mismatch - consider restructuring to avoid unsafe type casting

if (DEFAULT_EMAIL_THEME_ID in themeList) {
newActiveTheme = DEFAULT_EMAIL_THEME_ID;
} else {
newActiveTheme = Object.keys(themeList)[0];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes themeList has at least one entry when falling back to Object.keys(themeList)[0]. If themeList is empty, this will assign undefined to newActiveTheme, which will cause a runtime error when used as a configuration value. The code should validate that themeList is not empty before accessing the first key.

Suggested change
newActiveTheme = Object.keys(themeList)[0];
newActiveTheme = Object.keys(themeList).length > 0 ? Object.keys(themeList)[0] : DEFAULT_EMAIL_THEME_ID;

React with 👍 to tell me that this comment was useful, or 👎 if not (and I'll stop posting more comments like this in the future)

Copy link

recurseml bot commented Jul 18, 2025

😱 Found 1 issue. Time to roll up your sleeves! 😱

🗒️ View all ignored comments in this repo
  • The constraint 'TokenStoreType extends string' is too restrictive. It should likely be 'TokenStoreType extends string | object' to match the condition check in line 113 where TokenStoreType is checked against {}
  • Return type mismatch - the interface declares useUsers() returning ServerUser[] but the Team interface that this extends declares useUsers() returning TeamUser[]
  • There is a syntax error in the super constructor call due to the ellipsis operator used incorrectly. Objects aren't being merged correctly. This syntax usage can lead to runtime errors when trying to pass the merged object to 'super()'. Verify that the intended alterations to the object occur before or outside of the super() call if needed.
  • Throwing an error when no active span is found is too aggressive. The log function should gracefully fallback to console.log or another logging mechanism when there's no active span, since not all execution contexts will have an active span. This makes the code less resilient and could break functionality in non-traced environments.

📚 Relevant Docs

  • Function sets backendContext with a new configuration but doesn't pass 'defaultProjectKeys'. Since defaultProjectKeys is required in the type definition and cannot be updated (throws error if tried to set), this will cause a type error.
  • The schema is using array syntax for pick() which is incorrect for Yup schemas. The pick() method in Yup expects individual arguments, not an array. Should be changed to: emailConfigSchema.pick('type', 'host', 'port', 'username', 'sender_name', 'sender_email')

📚 Relevant Docs

  • Creating a refresh token with current timestamp as expiration means it expires immediately. Should set a future date for token expiration.
  • The 'tools' object is initialized as an empty object, even though 'tools' is presumably expected to contain tool definitions. This could cause the server capabilities to lack necessary tool configurations, thus potentially impacting functionalities that depend on certain tool setups.

📚 Relevant Docs

  • 'STACK_SECRET_SERVER_KEY' is potentially being included in every request header without checking its existence again here. Although it's checked during initialization, this could lead to security issues as it's exposed in all communications where the header is logged or captured.

📚 Relevant Docs

  • When adding 'use client' directive at the beginning, it doesn't check if file.text already contains the 'use client' directive. This could lead to duplicate 'use client' directives if the file already has one.

📚 Relevant Docs

⚠️ Only 5 files were analyzed due to processing limits.

Need help? Join our Discord for support!
https://discord.gg/qEjHQk64Z9

@@ -67,10 +67,10 @@ export function createHistoryAdapter(
return {
async load() {
const { messages } = await adminApp.listChatMessages(threadId);
return { messages } as ExportedMessageRepository;
return { messages } as unknown as ExportedMessageRepository;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid casting with 'as unknown as ...' in type conversions; define proper types to ensure type safety for message repositories and chat messages.

updateNewEmailTemplate(id: string, tsxSource: string): Promise<{ renderedHtml: string }>,

getProjectIdsForTemplatesMigration(): Promise<string[]>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to add end-to-end tests for new backend endpoints (e.g. email templates migration and theme endpoints) to cover the updated behavior.

This comment was generated because it violated a code review rule: mrule_PmPnAQVEfJISxhHC.

@BilalG1 BilalG1 changed the title Template migrations bg Template migrations Jul 18, 2025
@BilalG1 BilalG1 closed this Jul 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant