From f22f697edc1c826ad09c6fffba83a022415e4c28 Mon Sep 17 00:00:00 2001
From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Date: Tue, 11 Nov 2025 15:50:01 +0000
Subject: [PATCH] init user flags (#4875)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Description of Changes
Adds isNewServer and isNewUser flags to the /api/v1/app-config endpoint
to enable differentiated onboarding
experiences for first-time servers and users.
When onboarding completes → call POST
/api/v1/user/complete-initial-setup
---
## Checklist
### General
- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings
### Documentation
- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)
### Translations (if applicable)
- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)
### UI Changes (if applicable)
- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)
### Testing (if applicable)
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing)
for more details.
---
.../common/service/UserServiceInterface.java | 2 ++
.../software/SPDF/config/InitialSetup.java | 13 +++++++++
.../controller/api/misc/ConfigController.java | 17 ++++++++++++
.../controller/api/UserController.java | 27 +++++++++++++++++++
.../proprietary/security/model/User.java | 11 ++++++++
.../security/service/UserService.java | 15 +++++++++++
6 files changed, 85 insertions(+)
diff --git a/app/common/src/main/java/stirling/software/common/service/UserServiceInterface.java b/app/common/src/main/java/stirling/software/common/service/UserServiceInterface.java
index a833d4c84..074f42200 100644
--- a/app/common/src/main/java/stirling/software/common/service/UserServiceInterface.java
+++ b/app/common/src/main/java/stirling/software/common/service/UserServiceInterface.java
@@ -8,4 +8,6 @@ public interface UserServiceInterface {
long getTotalUsersCount();
boolean isCurrentUserAdmin();
+
+ boolean isCurrentUserFirstLogin();
}
diff --git a/app/core/src/main/java/stirling/software/SPDF/config/InitialSetup.java b/app/core/src/main/java/stirling/software/SPDF/config/InitialSetup.java
index 2d261c660..0a63a6f48 100644
--- a/app/core/src/main/java/stirling/software/SPDF/config/InitialSetup.java
+++ b/app/core/src/main/java/stirling/software/SPDF/config/InitialSetup.java
@@ -28,6 +28,8 @@ public class InitialSetup {
private final ApplicationProperties applicationProperties;
+ private static boolean isNewServer = false;
+
@PostConstruct
public void init() throws IOException {
initUUIDKey();
@@ -88,6 +90,13 @@ public class InitialSetup {
}
public void initSetAppVersion() throws IOException {
+ // Check if this is a new server before setting the version
+ String existingVersion = applicationProperties.getAutomaticallyGenerated().getAppVersion();
+ isNewServer =
+ existingVersion == null
+ || existingVersion.isEmpty()
+ || existingVersion.equals("0.0.0");
+
String appVersion = "0.0.0";
Resource resource = new ClassPathResource("version.properties");
Properties props = new Properties();
@@ -99,4 +108,8 @@ public class InitialSetup {
GeneralUtils.saveKeyToSettings("AutomaticallyGenerated.appVersion", appVersion);
applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);
}
+
+ public static boolean isNewServer() {
+ return isNewServer;
+ }
}
diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java
index b578d7c42..02b5233b8 100644
--- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java
+++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java
@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import io.swagger.v3.oas.annotations.Hidden;
import stirling.software.SPDF.config.EndpointConfiguration;
+import stirling.software.SPDF.config.InitialSetup;
import stirling.software.common.annotations.api.ConfigApi;
import stirling.software.common.configuration.AppConfig;
import stirling.software.common.model.ApplicationProperties;
@@ -78,6 +79,22 @@ public class ConfigController {
}
configData.put("isAdmin", isAdmin);
+ // Check if this is a new server (version was 0.0.0 before initialization)
+ configData.put("isNewServer", InitialSetup.isNewServer());
+
+ // Check if the current user is a first-time user
+ boolean isNewUser =
+ false; // Default to false when security is disabled or user not found
+ if (userService != null) {
+ try {
+ isNewUser = userService.isCurrentUserFirstLogin();
+ } catch (Exception e) {
+ // If there's an error, assume not new user for safety
+ isNewUser = false;
+ }
+ }
+ configData.put("isNewUser", isNewUser);
+
// System settings
configData.put(
"enableAlphaFunctionality",
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java
index 92a1f82ac..9f2ce9456 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java
@@ -742,4 +742,31 @@ public class UserController {
return errorMessage;
}
}
+
+ @PostMapping("/complete-initial-setup")
+ public ResponseEntity> completeInitialSetup() {
+ try {
+ String username = userService.getCurrentUsername();
+ if (username == null) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+ .body("User not authenticated");
+ }
+
+ Optional userOpt = userService.findByUsernameIgnoreCase(username);
+ if (userOpt.isEmpty()) {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found");
+ }
+
+ User user = userOpt.get();
+ user.setHasCompletedInitialSetup(true);
+ userRepository.save(user);
+
+ log.info("User {} completed initial setup", username);
+ return ResponseEntity.ok().body(Map.of("success", true));
+ } catch (Exception e) {
+ log.error("Error completing initial setup", e);
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body("Failed to complete initial setup");
+ }
+ }
}
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java
index 02bd08a5b..8f64d3187 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java
@@ -56,6 +56,9 @@ public class User implements UserDetails, Serializable {
@Column(name = "isFirstLogin")
private Boolean isFirstLogin = false;
+ @Column(name = "hasCompletedInitialSetup")
+ private Boolean hasCompletedInitialSetup = false;
+
@Column(name = "roleName")
private String roleName;
@@ -103,6 +106,14 @@ public class User implements UserDetails, Serializable {
this.isFirstLogin = isFirstLogin;
}
+ public boolean hasCompletedInitialSetup() {
+ return hasCompletedInitialSetup != null && hasCompletedInitialSetup;
+ }
+
+ public void setHasCompletedInitialSetup(boolean hasCompletedInitialSetup) {
+ this.hasCompletedInitialSetup = hasCompletedInitialSetup;
+ }
+
public void setAuthenticationType(AuthenticationType authenticationType) {
this.authenticationType = authenticationType.toString().toLowerCase();
}
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java
index d13fcc0cd..4772368f8 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java
@@ -663,6 +663,21 @@ public class UserService implements UserServiceInterface {
return false;
}
+ public boolean isCurrentUserFirstLogin() {
+ try {
+ String username = getCurrentUsername();
+ if (username != null) {
+ Optional userOpt = findByUsernameIgnoreCase(username);
+ if (userOpt.isPresent()) {
+ return !userOpt.get().hasCompletedInitialSetup();
+ }
+ }
+ } catch (Exception e) {
+ log.debug("Error checking first login status", e);
+ }
+ return false;
+ }
+
@Transactional
public void syncCustomApiUser(String customApiKey) {
if (customApiKey == null || customApiKey.trim().isBlank()) {