mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-04-22 23:08:53 +02:00
Merge branch 'V2' into settingsPageEnhanced
Resolved conflicts by: - Kept invite link feature (inviteLinkExpiryHours in ApplicationProperties) - Kept enhanced email invite validation (both SMTP and invites enabled check) - Adopted V2's Posthog/Scarf tracking additions - Adopted V2's password length validation (min 6 chars) on both backend and frontend - Converted email templates to Java text blocks - Kept success message functionality in Login flow - Used @app path aliases consistently across frontend files - Fixed remaining relative imports in Workbench, RestartConfirmationModal, useRestartServer, and InviteAccept - Accepted deletion of refactored files (App.tsx, Landing.tsx, etc.) - Kept InviteAccept.tsx for invite link feature
This commit is contained in:
@@ -355,6 +355,8 @@ public class ApplicationProperties {
|
||||
private String tessdataDir;
|
||||
private Boolean enableAlphaFunctionality;
|
||||
private Boolean enableAnalytics;
|
||||
private Boolean enablePosthog;
|
||||
private Boolean enableScarf;
|
||||
private Datasource datasource;
|
||||
private Boolean disableSanitize;
|
||||
private int maxDPI;
|
||||
@@ -372,6 +374,18 @@ public class ApplicationProperties {
|
||||
public boolean isAnalyticsEnabled() {
|
||||
return this.getEnableAnalytics() != null && this.getEnableAnalytics();
|
||||
}
|
||||
|
||||
public boolean isPosthogEnabled() {
|
||||
// Treat null as enabled when analytics is enabled
|
||||
return this.isAnalyticsEnabled()
|
||||
&& (this.getEnablePosthog() == null || this.getEnablePosthog());
|
||||
}
|
||||
|
||||
public boolean isScarfEnabled() {
|
||||
// Treat null as enabled when analytics is enabled
|
||||
return this.isAnalyticsEnabled()
|
||||
&& (this.getEnableScarf() == null || this.getEnableScarf());
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -56,7 +56,7 @@ public class PostHogService {
|
||||
}
|
||||
|
||||
private void captureSystemInfo() {
|
||||
if (!applicationProperties.getSystem().isAnalyticsEnabled()) {
|
||||
if (!applicationProperties.getSystem().isPosthogEnabled()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -67,7 +67,7 @@ public class PostHogService {
|
||||
}
|
||||
|
||||
public void captureEvent(String eventName, Map<String, Object> properties) {
|
||||
if (!applicationProperties.getSystem().isAnalyticsEnabled()) {
|
||||
if (!applicationProperties.getSystem().isPosthogEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,6 +325,14 @@ public class PostHogService {
|
||||
properties,
|
||||
"system_enableAnalytics",
|
||||
applicationProperties.getSystem().isAnalyticsEnabled());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_enablePosthog",
|
||||
applicationProperties.getSystem().isPosthogEnabled());
|
||||
addIfNotEmpty(
|
||||
properties,
|
||||
"system_enableScarf",
|
||||
applicationProperties.getSystem().isScarfEnabled());
|
||||
|
||||
// Capture UI properties
|
||||
addIfNotEmpty(
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
|
||||
@@ -31,7 +32,7 @@ public class SettingsController {
|
||||
|
||||
@AutoJobPostMapping("/update-enable-analytics")
|
||||
@Hidden
|
||||
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
||||
public ResponseEntity<String> updateApiKey(@RequestParam Boolean enabled) throws IOException {
|
||||
if (applicationProperties.getSystem().getEnableAnalytics() != null) {
|
||||
return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)
|
||||
.body(
|
||||
|
||||
@@ -84,6 +84,8 @@ public class ConfigController {
|
||||
applicationProperties.getSystem().getEnableAlphaFunctionality());
|
||||
configData.put(
|
||||
"enableAnalytics", applicationProperties.getSystem().getEnableAnalytics());
|
||||
configData.put("enablePosthog", applicationProperties.getSystem().getEnablePosthog());
|
||||
configData.put("enableScarf", applicationProperties.getSystem().getEnableScarf());
|
||||
|
||||
// Premium/Enterprise settings
|
||||
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
|
||||
|
||||
@@ -107,8 +107,8 @@ mail:
|
||||
from: '' # sender email address
|
||||
|
||||
legal:
|
||||
termsAndConditions: https://www.stirlingpdf.com/terms # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
|
||||
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
|
||||
termsAndConditions: https://www.stirling.com/legal/terms-of-service # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
|
||||
privacyPolicy: https://www.stirling.com/legal/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
|
||||
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility). Empty string to disable or filename to load from local file in static folder
|
||||
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie). Empty string to disable or filename to load from local file in static folder
|
||||
impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum). Empty string to disable or filename to load from local file in static folder
|
||||
@@ -121,7 +121,9 @@ system:
|
||||
showUpdateOnlyAdmin: false # only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files
|
||||
tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored.
|
||||
enableAnalytics: null # set to 'true' to enable analytics, set to 'false' to disable analytics; for enterprise users, this is set to true
|
||||
enableAnalytics: null # Master toggle for analytics: set to 'true' to enable all analytics, 'false' to disable all analytics, or leave as 'null' to prompt admin on first launch
|
||||
enablePosthog: null # Enable PostHog analytics (open-source product analytics): set to 'true' to enable, 'false' to disable, or 'null' to enable by default when analytics is enabled
|
||||
enableScarf: null # Enable Scarf tracking pixel: set to 'true' to enable, 'false' to disable, or 'null' to enable by default when analytics is enabled
|
||||
enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally
|
||||
disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML)
|
||||
maxDPI: 500 # Maximum allowed DPI for PDF to image conversion
|
||||
|
||||
@@ -366,6 +366,10 @@ public class UserController {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "Password is required."));
|
||||
}
|
||||
if (password.length() < 6) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(Map.of("error", "Password must be at least 6 characters."));
|
||||
}
|
||||
userService.saveUser(username, password, effectiveTeamId, role, forceChange);
|
||||
}
|
||||
return ResponseEntity.ok(Map.of("message", "User created successfully"));
|
||||
@@ -458,41 +462,12 @@ public class UserController {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Validate email format (basic check)
|
||||
if (!email.contains("@") || !email.contains(".")) {
|
||||
errors.append(email).append(": Invalid email format; ");
|
||||
failureCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
if (userService.usernameExistsIgnoreCase(email)) {
|
||||
errors.append(email).append(": User already exists; ");
|
||||
failureCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate random password
|
||||
String temporaryPassword = java.util.UUID.randomUUID().toString().substring(0, 12);
|
||||
|
||||
// Create user with forceChange=true
|
||||
userService.saveUser(email, temporaryPassword, effectiveTeamId, role, true);
|
||||
|
||||
// Send invite email
|
||||
try {
|
||||
emailService.get().sendInviteEmail(email, email, temporaryPassword);
|
||||
successCount++;
|
||||
log.info("Sent invite email to: {}", email);
|
||||
} catch (Exception emailEx) {
|
||||
log.error("Failed to send invite email to {}: {}", email, emailEx.getMessage());
|
||||
errors.append(email).append(": User created but email failed to send; ");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invite user {}: {}", email, e.getMessage());
|
||||
errors.append(email).append(": ").append(e.getMessage()).append("; ");
|
||||
InviteResult result = processEmailInvite(email, effectiveTeamId, role);
|
||||
if (result.isSuccess()) {
|
||||
successCount++;
|
||||
} else {
|
||||
failureCount++;
|
||||
errors.append(result.getErrorMessage()).append("; ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -685,4 +660,73 @@ public class UserController {
|
||||
}
|
||||
return ResponseEntity.ok(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to process a single email invitation.
|
||||
*
|
||||
* @param email The email address to invite
|
||||
* @param teamId The team ID to assign the user to
|
||||
* @param role The role to assign to the user
|
||||
* @return InviteResult containing success status and optional error message
|
||||
*/
|
||||
private InviteResult processEmailInvite(String email, Long teamId, String role) {
|
||||
try {
|
||||
// Validate email format (basic check)
|
||||
if (!email.contains("@") || !email.contains(".")) {
|
||||
return InviteResult.failure(email + ": Invalid email format");
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
if (userService.usernameExistsIgnoreCase(email)) {
|
||||
return InviteResult.failure(email + ": User already exists");
|
||||
}
|
||||
|
||||
// Generate random password
|
||||
String temporaryPassword = java.util.UUID.randomUUID().toString().substring(0, 12);
|
||||
|
||||
// Create user with forceChange=true
|
||||
userService.saveUser(email, temporaryPassword, teamId, role, true);
|
||||
|
||||
// Send invite email
|
||||
try {
|
||||
emailService.get().sendInviteEmail(email, email, temporaryPassword);
|
||||
log.info("Sent invite email to: {}", email);
|
||||
return InviteResult.success();
|
||||
} catch (Exception emailEx) {
|
||||
log.error("Failed to send invite email to {}: {}", email, emailEx.getMessage());
|
||||
return InviteResult.failure(email + ": User created but email failed to send");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to invite user {}: {}", email, e.getMessage());
|
||||
return InviteResult.failure(email + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Result object for individual email invite processing. */
|
||||
private static class InviteResult {
|
||||
private final boolean success;
|
||||
private final String errorMessage;
|
||||
|
||||
private InviteResult(boolean success, String errorMessage) {
|
||||
this.success = success;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
static InviteResult success() {
|
||||
return new InviteResult(true, null);
|
||||
}
|
||||
|
||||
static InviteResult failure(String errorMessage) {
|
||||
return new InviteResult(false, errorMessage);
|
||||
}
|
||||
|
||||
boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,38 +123,39 @@ public class EmailService {
|
||||
String subject = "Welcome to Stirling PDF";
|
||||
|
||||
String body =
|
||||
String.format(
|
||||
"<html><body style=\"margin: 0; padding: 0;\">"
|
||||
+ "<div style=\"font-family: Arial, sans-serif; background-color: #f8f9fa; padding: 20px;\">"
|
||||
+ " <div style=\"max-width: 600px; margin: auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; border: 1px solid #e0e0e0;\">"
|
||||
+ " <!-- Logo -->"
|
||||
+ " <div style=\"text-align: center; padding: 20px; background-color: #222;\">"
|
||||
+ " <img src=\"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling-transparent.svg\" alt=\"Stirling PDF\" style=\"max-height: 60px;\">"
|
||||
+ " </div>"
|
||||
+ " <!-- Content -->"
|
||||
+ " <div style=\"padding: 30px; color: #333;\">"
|
||||
+ " <h2 style=\"color: #222; margin-top: 0;\">Welcome to Stirling PDF!</h2>"
|
||||
+ " <p>Hi there,</p>"
|
||||
+ " <p>You have been invited to join the workspace. Below are your login credentials:</p>"
|
||||
+ " <!-- Credentials Box -->"
|
||||
+ " <div style=\"background-color: #f8f9fa; border-left: 4px solid #007bff; padding: 15px; margin: 20px 0; border-radius: 4px;\">"
|
||||
+ " <p style=\"margin: 0 0 10px 0;\"><strong>Username:</strong> %s</p>"
|
||||
+ " <p style=\"margin: 0;\"><strong>Temporary Password:</strong> %s</p>"
|
||||
+ " </div>"
|
||||
+ " <div style=\"background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; border-radius: 4px;\">"
|
||||
+ " <p style=\"margin: 0; color: #856404;\"><strong>⚠️ Important:</strong> You will be required to change your password upon first login for security reasons.</p>"
|
||||
+ " </div>"
|
||||
+ " <p>Please keep these credentials secure and do not share them with anyone.</p>"
|
||||
+ " <p style=\"margin-bottom: 0;\">— The Stirling PDF Team</p>"
|
||||
+ " </div>"
|
||||
+ " <!-- Footer -->"
|
||||
+ " <div style=\"text-align: center; padding: 15px; font-size: 12px; color: #777; background-color: #f0f0f0;\">"
|
||||
+ " © 2025 Stirling PDF. All rights reserved."
|
||||
+ " </div>"
|
||||
+ " </div>"
|
||||
+ "</div>"
|
||||
+ "</body></html>",
|
||||
username, temporaryPassword);
|
||||
"""
|
||||
<html><body style="margin: 0; padding: 0;">
|
||||
<div style="font-family: Arial, sans-serif; background-color: #f8f9fa; padding: 20px;">
|
||||
<div style="max-width: 600px; margin: auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; border: 1px solid #e0e0e0;">
|
||||
<!-- Logo -->
|
||||
<div style="text-align: center; padding: 20px; background-color: #222;">
|
||||
<img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling-transparent.svg" alt="Stirling PDF" style="max-height: 60px;">
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div style="padding: 30px; color: #333;">
|
||||
<h2 style="color: #222; margin-top: 0;">Welcome to Stirling PDF!</h2>
|
||||
<p>Hi there,</p>
|
||||
<p>You have been invited to join the workspace. Below are your login credentials:</p>
|
||||
<!-- Credentials Box -->
|
||||
<div style="background-color: #f8f9fa; border-left: 4px solid #007bff; padding: 15px; margin: 20px 0; border-radius: 4px;">
|
||||
<p style="margin: 0 0 10px 0;"><strong>Username:</strong> %s</p>
|
||||
<p style="margin: 0;"><strong>Temporary Password:</strong> %s</p>
|
||||
</div>
|
||||
<div style="background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; border-radius: 4px;">
|
||||
<p style="margin: 0; color: #856404;"><strong>⚠️ Important:</strong> You will be required to change your password upon first login for security reasons.</p>
|
||||
</div>
|
||||
<p>Please keep these credentials secure and do not share them with anyone.</p>
|
||||
<p style="margin-bottom: 0;">— The Stirling PDF Team</p>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div style="text-align: center; padding: 15px; font-size: 12px; color: #777; background-color: #f0f0f0;">
|
||||
© 2025 Stirling PDF. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>
|
||||
"""
|
||||
.formatted(username, temporaryPassword);
|
||||
|
||||
sendPlainEmail(to, subject, body, true);
|
||||
}
|
||||
@@ -173,41 +174,42 @@ public class EmailService {
|
||||
String subject = "You've been invited to Stirling PDF";
|
||||
|
||||
String body =
|
||||
String.format(
|
||||
"<html><body style=\"margin: 0; padding: 0;\">"
|
||||
+ "<div style=\"font-family: Arial, sans-serif; background-color: #f8f9fa; padding: 20px;\">"
|
||||
+ " <div style=\"max-width: 600px; margin: auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; border: 1px solid #e0e0e0;\">"
|
||||
+ " <!-- Logo -->"
|
||||
+ " <div style=\"text-align: center; padding: 20px; background-color: #222;\">"
|
||||
+ " <img src=\"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling-transparent.svg\" alt=\"Stirling PDF\" style=\"max-height: 60px;\">"
|
||||
+ " </div>"
|
||||
+ " <!-- Content -->"
|
||||
+ " <div style=\"padding: 30px; color: #333;\">"
|
||||
+ " <h2 style=\"color: #222; margin-top: 0;\">Welcome to Stirling PDF!</h2>"
|
||||
+ " <p>Hi there,</p>"
|
||||
+ " <p>You have been invited to join the Stirling PDF workspace. Click the button below to set up your account:</p>"
|
||||
+ " <!-- CTA Button -->"
|
||||
+ " <div style=\"text-align: center; margin: 30px 0;\">"
|
||||
+ " <a href=\"%s\" style=\"display: inline-block; background-color: #007bff; color: #ffffff; padding: 14px 28px; text-decoration: none; border-radius: 5px; font-weight: bold;\">Accept Invitation</a>"
|
||||
+ " </div>"
|
||||
+ " <p style=\"font-size: 14px; color: #666;\">Or copy and paste this link in your browser:</p>"
|
||||
+ " <div style=\"background-color: #f8f9fa; padding: 12px; margin: 15px 0; border-radius: 4px; word-break: break-all; font-size: 13px; color: #555;\">"
|
||||
+ " %s"
|
||||
+ " </div>"
|
||||
+ " <div style=\"background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; border-radius: 4px;\">"
|
||||
+ " <p style=\"margin: 0; color: #856404; font-size: 14px;\"><strong>⚠️ Important:</strong> This invitation link will expire on %s. Please complete your registration before then.</p>"
|
||||
+ " </div>"
|
||||
+ " <p>If you didn't expect this invitation, you can safely ignore this email.</p>"
|
||||
+ " <p style=\"margin-bottom: 0;\">— The Stirling PDF Team</p>"
|
||||
+ " </div>"
|
||||
+ " <!-- Footer -->"
|
||||
+ " <div style=\"text-align: center; padding: 15px; font-size: 12px; color: #777; background-color: #f0f0f0;\">"
|
||||
+ " © 2025 Stirling PDF. All rights reserved."
|
||||
+ " </div>"
|
||||
+ " </div>"
|
||||
+ "</div>"
|
||||
+ "</body></html>",
|
||||
inviteUrl, inviteUrl, expiresAt);
|
||||
"""
|
||||
<html><body style="margin: 0; padding: 0;">
|
||||
<div style="font-family: Arial, sans-serif; background-color: #f8f9fa; padding: 20px;">
|
||||
<div style="max-width: 600px; margin: auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; border: 1px solid #e0e0e0;">
|
||||
<!-- Logo -->
|
||||
<div style="text-align: center; padding: 20px; background-color: #222;">
|
||||
<img src="https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/main/docs/stirling-transparent.svg" alt="Stirling PDF" style="max-height: 60px;">
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div style="padding: 30px; color: #333;">
|
||||
<h2 style="color: #222; margin-top: 0;">Welcome to Stirling PDF!</h2>
|
||||
<p>Hi there,</p>
|
||||
<p>You have been invited to join the Stirling PDF workspace. Click the button below to set up your account:</p>
|
||||
<!-- CTA Button -->
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="%s" style="display: inline-block; background-color: #007bff; color: #ffffff; padding: 14px 28px; text-decoration: none; border-radius: 5px; font-weight: bold;">Accept Invitation</a>
|
||||
</div>
|
||||
<p style="font-size: 14px; color: #666;">Or copy and paste this link in your browser:</p>
|
||||
<div style="background-color: #f8f9fa; padding: 12px; margin: 15px 0; border-radius: 4px; word-break: break-all; font-size: 13px; color: #555;">
|
||||
%s
|
||||
</div>
|
||||
<div style="background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; border-radius: 4px;">
|
||||
<p style="margin: 0; color: #856404; font-size: 14px;"><strong>⚠️ Important:</strong> This invitation link will expire on %s. Please complete your registration before then.</p>
|
||||
</div>
|
||||
<p>If you didn't expect this invitation, you can safely ignore this email.</p>
|
||||
<p style="margin-bottom: 0;">— The Stirling PDF Team</p>
|
||||
</div>
|
||||
<!-- Footer -->
|
||||
<div style="text-align: center; padding: 15px; font-size: 12px; color: #777; background-color: #f0f0f0;">
|
||||
© 2025 Stirling PDF. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body></html>
|
||||
"""
|
||||
.formatted(inviteUrl, inviteUrl, expiresAt);
|
||||
|
||||
sendPlainEmail(to, subject, body, true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user