diff --git a/onyx-api/src/main/java/com/andrewlalis/onyx/NodeInitializer.java b/onyx-api/src/main/java/com/andrewlalis/onyx/NodeInitializer.java
new file mode 100644
index 0000000..965b676
--- /dev/null
+++ b/onyx-api/src/main/java/com/andrewlalis/onyx/NodeInitializer.java
@@ -0,0 +1,68 @@
+package com.andrewlalis.onyx;
+
+import com.andrewlalis.onyx.auth.model.User;
+import com.andrewlalis.onyx.auth.model.UserRepository;
+import com.andrewlalis.onyx.content.dao.ContentNodeRepository;
+import com.andrewlalis.onyx.content.model.ContentContainerNode;
+import com.andrewlalis.onyx.content.model.ContentNode;
+import com.andrewlalis.onyx.content.model.access.ContentAccessLevel;
+import com.andrewlalis.onyx.content.model.access.UserContentAccessRule;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Component;
+
+/**
+ * A component that runs on application startup, to take care of some
+ * first-time initialization, if needed:
+ *
+ * - Create a default admin user, if none exists.
+ * - Create the root content node for the content tree.
+ *
+ */
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class NodeInitializer implements CommandLineRunner {
+ private final ContentNodeRepository contentNodeRepository;
+ private final UserRepository userRepository;
+ private final PasswordEncoder passwordEncoder;
+
+ @Override
+ public void run(String... args) throws Exception {
+ User adminUser = createAdminUserIfNoUsers();
+ createRootNodeIfNotExists(adminUser);
+ }
+
+ private void createRootNodeIfNotExists(User adminUser) {
+ if (!contentNodeRepository.existsByName(ContentNode.ROOT_NODE_NAME)) {
+ ContentNode rootNode = new ContentContainerNode(ContentNode.ROOT_NODE_NAME, null);
+ rootNode.getAccessInfo().setPublicAccessLevel(ContentAccessLevel.NONE);
+ rootNode.getAccessInfo().setNetworkAccessLevel(ContentAccessLevel.NONE);
+ rootNode.getAccessInfo().setNodeAccessLevel(ContentAccessLevel.NONE);
+ if (adminUser != null) {
+ rootNode.getAccessInfo().getUserAccessRules().add(new UserContentAccessRule(
+ adminUser,
+ rootNode.getAccessInfo(),
+ ContentAccessLevel.EDIT
+ ));
+ }
+ contentNodeRepository.saveAndFlush(rootNode);
+ log.info("Created content root container node.");
+ }
+ }
+
+ private User createAdminUserIfNoUsers() {
+ if (userRepository.count() == 0) {
+ User user = userRepository.saveAndFlush(new User(
+ "admin",
+ "Admin",
+ passwordEncoder.encode("onyx-admin")
+ ));
+ log.info("Created user \"admin\" with password \"onyx-admin\"");
+ return user;
+ }
+ return null;
+ }
+}
diff --git a/onyx-api/src/main/java/com/andrewlalis/onyx/auth/service/TokenService.java b/onyx-api/src/main/java/com/andrewlalis/onyx/auth/service/TokenService.java
index 29bcd91..a9f88f6 100644
--- a/onyx-api/src/main/java/com/andrewlalis/onyx/auth/service/TokenService.java
+++ b/onyx-api/src/main/java/com/andrewlalis/onyx/auth/service/TokenService.java
@@ -40,7 +40,7 @@ public class TokenService {
private static final String ISSUER = "Onyx API";
private PrivateKey signingKey;
- private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(10);
+ private final PasswordEncoder passwordEncoder;
private final RefreshTokenRepository refreshTokenRepository;
private final UserRepository userRepository;
diff --git a/onyx-api/src/main/java/com/andrewlalis/onyx/config/SecurityComponents.java b/onyx-api/src/main/java/com/andrewlalis/onyx/config/SecurityComponents.java
new file mode 100644
index 0000000..1b7c29d
--- /dev/null
+++ b/onyx-api/src/main/java/com/andrewlalis/onyx/config/SecurityComponents.java
@@ -0,0 +1,14 @@
+package com.andrewlalis.onyx.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class SecurityComponents {
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder(10);
+ }
+}
diff --git a/onyx-api/src/main/java/com/andrewlalis/onyx/content/ContentTreeInitializer.java b/onyx-api/src/main/java/com/andrewlalis/onyx/content/ContentTreeInitializer.java
deleted file mode 100644
index 45354a8..0000000
--- a/onyx-api/src/main/java/com/andrewlalis/onyx/content/ContentTreeInitializer.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.andrewlalis.onyx.content;
-
-import com.andrewlalis.onyx.content.dao.ContentNodeRepository;
-import com.andrewlalis.onyx.content.model.access.ContentAccessLevel;
-import com.andrewlalis.onyx.content.model.ContentContainerNode;
-import com.andrewlalis.onyx.content.model.ContentNode;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.stereotype.Component;
-
-@Component
-@RequiredArgsConstructor
-@Slf4j
-public class ContentTreeInitializer implements CommandLineRunner {
- private final ContentNodeRepository contentNodeRepository;
-
- @Override
- public void run(String... args) throws Exception {
- log.info("Check if content root container node exists.");
- if (!contentNodeRepository.existsByName(ContentNode.ROOT_NODE_NAME)) {
- ContentNode rootNode = new ContentContainerNode(ContentNode.ROOT_NODE_NAME, null);
- rootNode.getAccessInfo().setPublicAccessLevel(ContentAccessLevel.NONE);
- rootNode.getAccessInfo().setNetworkAccessLevel(ContentAccessLevel.NONE);
- rootNode.getAccessInfo().setNodeAccessLevel(ContentAccessLevel.NONE);
- contentNodeRepository.saveAndFlush(rootNode);
- log.info("Created content root container node.");
- }
- }
-}
diff --git a/onyx-api/src/main/java/com/andrewlalis/onyx/content/model/access/ContentAccessRules.java b/onyx-api/src/main/java/com/andrewlalis/onyx/content/model/access/ContentAccessRules.java
index fb63599..3d7be3c 100644
--- a/onyx-api/src/main/java/com/andrewlalis/onyx/content/model/access/ContentAccessRules.java
+++ b/onyx-api/src/main/java/com/andrewlalis/onyx/content/model/access/ContentAccessRules.java
@@ -5,6 +5,7 @@ import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -40,5 +41,5 @@ public class ContentAccessRules {
* User-specific access rules that override other more generic rules, if present.
*/
@OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "contentAccessRules")
- private Set userAccessRules;
+ private Set userAccessRules = new HashSet<>();
}
diff --git a/onyx-api/src/main/resources/application-development.properties b/onyx-api/src/main/resources/application-development.properties
index a684ac0..b64b8f5 100644
--- a/onyx-api/src/main/resources/application-development.properties
+++ b/onyx-api/src/main/resources/application-development.properties
@@ -2,4 +2,4 @@
spring.datasource.url=jdbc:h2:./onyx-db-dev
spring.datasource.driver-class-name=org.h2.Driver
-spring.jpa.hibernate.ddl-auto=create
+spring.jpa.hibernate.ddl-auto=update