diff --git a/.gitignore b/.gitignore
index 40f55760b0..204ca95f52 100755
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,5 @@ view/target
 *.log
 fitnesse/target/
 daisy-integration/target/
+war/target/
+api/target/
diff --git a/api/pom.xml b/api/pom.xml
new file mode 100644
index 0000000000..35c607e1b1
--- /dev/null
+++ b/api/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>se.su.dsv.scipro</groupId>
+        <artifactId>SciPro</artifactId>
+        <version>0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>api</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>se.su.dsv.scipro</groupId>
+            <artifactId>core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/view/src/main/java/se/su/dsv/scipro/api/ApiController.java b/api/src/main/java/se/su/dsv/scipro/api/ApiController.java
similarity index 100%
rename from view/src/main/java/se/su/dsv/scipro/api/ApiController.java
rename to api/src/main/java/se/su/dsv/scipro/api/ApiController.java
diff --git a/core/src/main/java/se/su/dsv/scipro/oauth/OAuthSettings.java b/core/src/main/java/se/su/dsv/scipro/oauth/OAuthSettings.java
index a8b41b8648..2a20bcf204 100644
--- a/core/src/main/java/se/su/dsv/scipro/oauth/OAuthSettings.java
+++ b/core/src/main/java/se/su/dsv/scipro/oauth/OAuthSettings.java
@@ -1,4 +1,4 @@
 package se.su.dsv.scipro.oauth;
 
-record OAuthSettings(String uri, String redirectUri, String clientId, String clientSecret) {
+public record OAuthSettings(String uri, String redirectUri, String clientId, String clientSecret) {
 }
diff --git a/core/src/main/java/se/su/dsv/scipro/profiles/CurrentProfile.java b/core/src/main/java/se/su/dsv/scipro/profiles/CurrentProfile.java
index 679ba53157..6bf6f2bd19 100755
--- a/core/src/main/java/se/su/dsv/scipro/profiles/CurrentProfile.java
+++ b/core/src/main/java/se/su/dsv/scipro/profiles/CurrentProfile.java
@@ -1,14 +1,10 @@
 package se.su.dsv.scipro.profiles;
 
-import jakarta.inject.Inject;
-import jakarta.inject.Named;
-
 public class CurrentProfile {
 
     private String currentProfileString;
 
-    @Inject
-    public void setCurrentProfileString(@Named("profile") String currentProfileString) {
+    public void setCurrentProfileString(String currentProfileString) {
         this.currentProfileString = currentProfileString;
     }
 
diff --git a/core/src/main/java/se/su/dsv/scipro/workerthreads/AbstractWorker.java b/core/src/main/java/se/su/dsv/scipro/workerthreads/AbstractWorker.java
index 751b6c8831..352c674419 100755
--- a/core/src/main/java/se/su/dsv/scipro/workerthreads/AbstractWorker.java
+++ b/core/src/main/java/se/su/dsv/scipro/workerthreads/AbstractWorker.java
@@ -1,6 +1,5 @@
 package se.su.dsv.scipro.workerthreads;
 
-import com.google.inject.persist.UnitOfWork;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -25,7 +24,6 @@ public abstract class AbstractWorker implements Worker {
     private boolean successfulWorker = true;
     private Provider<EntityManager> emProvider;
     private EntityTransaction transaction;
-    private UnitOfWork unitOfWork;
 
     /**
      * Subclasses must be annotated with @Component or similar annotation in order for autowiring of dependencies to work
@@ -43,14 +41,8 @@ public abstract class AbstractWorker implements Worker {
         this.emProvider = em;
     }
 
-    @Inject
-    public void setUnitOfWork(UnitOfWork unitOfWork) {
-        this.unitOfWork = unitOfWork;
-    }
-
     @Override
     public void run() {
-        unitOfWork.begin();
         EntityManager em = emProvider.get();
         try {
             transaction = em.getTransaction();
@@ -98,7 +90,6 @@ public abstract class AbstractWorker implements Worker {
             if (transaction.isActive() && em.isOpen()) {
                 transaction.rollback();
             }
-            unitOfWork.end();
         }
     }
 
diff --git a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/GradingCompletedMilestoneActivatorTest.java b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/GradingCompletedMilestoneActivatorTest.java
index b583b29e21..9c766ce817 100644
--- a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/GradingCompletedMilestoneActivatorTest.java
+++ b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/GradingCompletedMilestoneActivatorTest.java
@@ -58,7 +58,6 @@ public class GradingCompletedMilestoneActivatorTest {
     @BeforeEach
     public void setup() {
         gradingCompletedMilestoneActivator = new GradingCompletedMilestoneActivator(projectService, daisyAPI, eventBus, userService);
-        gradingCompletedMilestoneActivator.setUnitOfWork(unitOfWork);
         gradingCompletedMilestoneActivator.setTxManager(Providers.of(entityManager));
         gradingCompletedMilestoneActivator.setWorkerDataService(workerDataService);
         when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
diff --git a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectExporterTest.java b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectExporterTest.java
index f4165842bb..d2f53aca29 100644
--- a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectExporterTest.java
+++ b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectExporterTest.java
@@ -63,7 +63,6 @@ public class ProjectExporterTest {
         projectExporter = new ProjectExporter(projectRepo, exporterFacade, daisyAPI, externalExporter, finalSeminarService, finalThesisService);
         projectExporter.setTxManager(Providers.of(entityManager));
         projectExporter.setWorkerDataService(workerDataService);
-        projectExporter.setUnitOfWork(unitOfWork);
         when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
         when(entityManager.getTransaction()).thenReturn(entityTransaction);
 
diff --git a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectFinalizerTest.java b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectFinalizerTest.java
index 576409136a..52b7094897 100644
--- a/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectFinalizerTest.java
+++ b/daisy-integration/src/test/java/se/su/dsv/scipro/integration/daisy/workers/ProjectFinalizerTest.java
@@ -48,7 +48,6 @@ public class ProjectFinalizerTest {
     @BeforeEach
     public void setup() {
         projectFinalizer = new ProjectFinalizer(projectService, daisyAPI, thesisApprovedHistoryService);
-        projectFinalizer.setUnitOfWork(unitOfWork);
         projectFinalizer.setTxManager(Providers.of(entityManager));
         projectFinalizer.setWorkerDataService(workerDataService);
         when(workerDataService.save(any(WorkerData.class))).thenReturn(new WorkerData());
diff --git a/pom.xml b/pom.xml
index 4e6554cb35..2a0ddec0e3 100755
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,8 @@
         <module>core</module>
         <module>view</module>
         <module>daisy-integration</module>
+        <module>war</module>
+        <module>api</module>
     </modules>
 
     <properties>
diff --git a/view/pom.xml b/view/pom.xml
index d9bcb9393a..09d31eda79 100644
--- a/view/pom.xml
+++ b/view/pom.xml
@@ -46,10 +46,6 @@
             <groupId>org.apache.wicket</groupId>
             <artifactId>wicket-guice</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.wicket</groupId>
-            <artifactId>wicket-spring</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.apache.wicket</groupId>
             <artifactId>wicket-extensions</artifactId>
@@ -91,31 +87,6 @@
             <version>5.3.2</version>
         </dependency>
 
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-orm</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-tx</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-webmvc</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-config</artifactId>
-        </dependency>
-
         <!--  Servlet API, needed for compilation. -->
         <dependency>
             <groupId>jakarta.servlet</groupId>
@@ -173,6 +144,15 @@
     </dependencies>
 
     <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <configuration>
+                    <attachClasses>true</attachClasses>
+                </configuration>
+            </plugin>
+        </plugins>
         <resources>
             <resource>
                 <filtering>true</filtering>
diff --git a/view/src/main/java/ApplicationBootstrap.java b/view/src/main/java/ApplicationBootstrap.java
deleted file mode 100644
index 7b4897a09d..0000000000
--- a/view/src/main/java/ApplicationBootstrap.java
+++ /dev/null
@@ -1,322 +0,0 @@
-import com.google.common.eventbus.EventBus;
-import jakarta.inject.Named;
-import jakarta.inject.Provider;
-import jakarta.inject.Singleton;
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
-import jakarta.servlet.ServletContext;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRegistration;
-import org.apache.wicket.protocol.http.WicketFilter;
-import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.task.SimpleAsyncTaskExecutor;
-import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
-import org.springframework.orm.jpa.SharedEntityManagerCreator;
-import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
-import org.springframework.security.config.Customizer;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.provisioning.InMemoryUserDetailsManager;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.annotation.EnableTransactionManagement;
-import org.springframework.web.WebApplicationInitializer;
-import org.springframework.web.context.ContextLoaderListener;
-import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
-import org.springframework.web.filter.DelegatingFilterProxy;
-import org.springframework.web.servlet.DispatcherServlet;
-import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-import se.su.dsv.scipro.SciProApplication;
-import se.su.dsv.scipro.daisyExternal.http.DaisyAPI;
-import se.su.dsv.scipro.daisyExternal.http.DaisyAPIImpl;
-import se.su.dsv.scipro.file.FileService;
-import se.su.dsv.scipro.finalseminar.AuthorRepository;
-import se.su.dsv.scipro.finalseminar.FinalSeminarActiveParticipationRepository;
-import se.su.dsv.scipro.finalseminar.FinalSeminarOppositionRepo;
-import se.su.dsv.scipro.finalseminar.FinalSeminarRepository;
-import se.su.dsv.scipro.finalseminar.FinalSeminarService;
-import se.su.dsv.scipro.finalseminar.FinalSeminarServiceImpl;
-import se.su.dsv.scipro.finalseminar.FinalSeminarSettingsService;
-import se.su.dsv.scipro.finalseminar.FinalSeminarSettingsServiceImpl;
-import se.su.dsv.scipro.gdpr.Reporter;
-import se.su.dsv.scipro.misc.DaysService;
-import se.su.dsv.scipro.misc.DaysServiceImpl;
-import se.su.dsv.scipro.nonworkperiod.NonWorkDayPeriodService;
-import se.su.dsv.scipro.nonworkperiod.NonWorkDayPeriodServiceImpl;
-import se.su.dsv.scipro.notifications.NotificationService;
-import se.su.dsv.scipro.notifications.NotificationServiceImpl;
-import se.su.dsv.scipro.profiles.CurrentProfile;
-import se.su.dsv.scipro.project.ProjectRepo;
-import se.su.dsv.scipro.project.ProjectRepoImpl;
-import se.su.dsv.scipro.project.ProjectService;
-import se.su.dsv.scipro.project.ProjectServiceImpl;
-import se.su.dsv.scipro.report.OppositionReportService;
-import se.su.dsv.scipro.report.OppositionReportServiceImpl;
-import se.su.dsv.scipro.reviewing.RoughDraftApprovalService;
-import se.su.dsv.scipro.springdata.serviceimpls.UserProfileServiceImpl;
-import se.su.dsv.scipro.springdata.services.UserProfileService;
-import se.su.dsv.scipro.system.FooterAddressRepo;
-import se.su.dsv.scipro.system.FooterAddressRepoImpl;
-import se.su.dsv.scipro.system.FooterLinkRepo;
-import se.su.dsv.scipro.system.FooterLinkRepoImpl;
-import se.su.dsv.scipro.system.FooterLinkService;
-import se.su.dsv.scipro.system.FooterLinkServiceImpl;
-import se.su.dsv.scipro.system.Lifecycle;
-import se.su.dsv.scipro.system.ResearchArea;
-import se.su.dsv.scipro.system.User;
-import se.su.dsv.scipro.system.UserImportService;
-import se.su.dsv.scipro.system.UserService;
-import se.su.dsv.scipro.system.UserServiceImpl;
-
-import java.net.CookieHandler;
-import java.net.CookieManager;
-import java.time.Clock;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-public class ApplicationBootstrap implements WebApplicationInitializer {
-    @Override
-    public void onStartup(ServletContext servletContext)
-            throws ServletException
-    {
-        // preserve cookies when using Jersey which will use the logged in session cookie from
-        // Daisy API thus drastically speeding up the batch jobs
-        CookieHandler.setDefault(new CookieManager());
-
-        AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
-        webApplicationContext.setServletContext(servletContext);
-        webApplicationContext.register(SciProApplication.class);
-        webApplicationContext.register(CurrentProfile.class);
-        webApplicationContext.register(Config.class);
-        webApplicationContext.refresh();
-
-        servletContext.addListener(new ContextLoaderListener(webApplicationContext));
-
-        SciProApplication sciProApplication = webApplicationContext.getBean(SciProApplication.class);
-        sciProApplication.getComponentInstantiationListeners()
-                .add(new SpringComponentInjector(sciProApplication, webApplicationContext));
-
-        servletContext.addFilter("osiv", OpenEntityManagerInViewFilter.class)
-                .addMappingForUrlPatterns(null, false, "/*");
-
-        WicketFilter filter = new WicketFilter(sciProApplication);
-        filter.setFilterPath("");
-
-        servletContext.addFilter("wicket-filter", filter)
-                .addMappingForUrlPatterns(null, true, "/*");
-
-        AnnotationConfigWebApplicationContext dispatcherApplicationContext = new AnnotationConfigWebApplicationContext();
-        dispatcherApplicationContext.register(WebConfig.class);
-        dispatcherApplicationContext.setParent(webApplicationContext);
-        dispatcherApplicationContext.setServletContext(servletContext);
-
-        ServletRegistration.Dynamic dispatcher = servletContext.addServlet(
-                "spring-web",
-                new DispatcherServlet(dispatcherApplicationContext));
-        dispatcher.setLoadOnStartup(1);
-        dispatcher.addMapping("/api/*");
-
-        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy("springSecurityFilterChain", dispatcherApplicationContext);
-        servletContext.addFilter("spring-security-filter", springSecurityFilterChain)
-                .addMappingForServletNames(null, false, "spring-web");
-    }
-
-    @Configuration
-    @EnableWebMvc
-    @EnableWebSecurity
-    @ComponentScan("se.su.dsv.scipro.api")
-    public static class WebConfig {
-        @Bean
-        public SecurityFilterChain basicAuth(HttpSecurity http)
-                throws Exception
-        {
-            return http
-                    .httpBasic(Customizer.withDefaults())
-                    .authorizeHttpRequests(authorize -> authorize
-                            .anyRequest().authenticated())
-                    .build();
-        }
-
-        @Bean
-        public UserDetailsService userDetailsService() {
-            UserDetails userDetails = org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder()
-                    .username("user")
-                    .password("password")
-                    .roles("USER")
-                    .build();
-
-            return new InMemoryUserDetailsManager(userDetails);
-        }
-    }
-
-    @Configuration
-    @EnableTransactionManagement
-    public static class Config {
-        @Bean
-        @Named("profile")
-        public String test() {
-            return "DEV";
-        }
-
-        @Bean
-        public Lifecycle dummy() {
-            return new Lifecycle() {
-                @Override
-                public void start() {
-
-                }
-
-                @Override
-                public void stop() {
-
-                }
-            };
-        }
-
-        @Bean
-        public Reporter reporter() {
-            return user -> null;
-        }
-
-        @Bean
-        @Singleton
-        public LocalEntityManagerFactoryBean entityManagerFactory() {
-            LocalEntityManagerFactoryBean localEntityManagerFactoryBean = new LocalEntityManagerFactoryBean();
-            localEntityManagerFactoryBean.setPersistenceUnitName("defaultPersistenceUnit");
-            localEntityManagerFactoryBean.setBootstrapExecutor(new SimpleAsyncTaskExecutor());
-            return localEntityManagerFactoryBean;
-        }
-
-        @Bean
-        public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
-            return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory);
-        }
-
-        @Bean
-        public UserService userService(Provider<EntityManager> entityManagerProvider) {
-            return new UserServiceImpl(entityManagerProvider);
-        }
-
-        @Bean
-        public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
-            return new org.springframework.orm.jpa.JpaTransactionManager(entityManagerFactory);
-        }
-
-        @Bean
-        public UserImportService importService() {
-            return new UserImportService() {
-                @Override
-                public Optional<User> importUser(String userName) {
-                    return Optional.empty();
-                }
-
-                @Override
-                public List<ImportableUser> search(String searchTerm) {
-                    return List.of();
-                }
-
-                @Override
-                public void importUser(ImportableUser importableUser) {
-                }
-
-                @Override
-                public Set<ResearchArea> importResearchAreasForSupervisor(User supervisor) {
-                    return Set.of();
-                }
-            };
-        }
-
-        @Bean
-        public UserProfileService userProfileService(Provider<EntityManager> em) {
-            return new UserProfileServiceImpl(em);
-        }
-
-        @Bean
-        public NotificationService notificationService(Provider<EntityManager> em) {
-            return new NotificationServiceImpl(em);
-        }
-
-        @Bean
-        public FooterLinkService footerLinkService(FooterLinkRepo repository) {
-            return new FooterLinkServiceImpl(repository);
-        }
-
-        @Bean
-        public FooterLinkRepo footerLinkRepo(Provider<EntityManager> em) {
-            return new FooterLinkRepoImpl(em);
-        }
-
-        @Bean
-        public FooterAddressRepo footerAddressRepo(Provider<EntityManager> em) {
-            return new FooterAddressRepoImpl(em);
-        }
-
-        @Bean
-        public ProjectService projectService(Provider<EntityManager> em, ProjectRepo projectRepo, Clock clock,
-                EventBus eventBus) {
-            return new ProjectServiceImpl(projectRepo, clock, eventBus, em);
-        }
-
-        @Bean
-        public ProjectRepo projectRepo(Provider<EntityManager> em) {
-            return new ProjectRepoImpl(em);
-        }
-
-        @Bean
-        public Clock clock() {
-            return Clock.systemDefaultZone();
-        }
-
-        @Bean
-        public EventBus eventBus() {
-            return new EventBus();
-        }
-
-        @Bean
-        public FinalSeminarService finalSeminarService(Provider<EntityManager> em, EventBus eventBus, Clock clock) {
-            AuthorRepository authorRepository = null;
-            FileService fileService = null;
-            FinalSeminarOppositionRepo finalSeminarOppositionRepository = null;
-            FinalSeminarActiveParticipationRepository finalSeminarActiveParticipantRepository = null;
-            FinalSeminarRepository finalSeminarRepository = null;
-            RoughDraftApprovalService roughDraftApprovalService = null;
-            return new FinalSeminarServiceImpl(em, eventBus, authorRepository, fileService, finalSeminarOppositionRepository, finalSeminarActiveParticipantRepository, finalSeminarRepository, clock, roughDraftApprovalService);
-        }
-
-        @Bean
-        public FinalSeminarSettingsService finalSeminarSettingsService(Provider<EntityManager> em) {
-            return new FinalSeminarSettingsServiceImpl(em);
-        }
-
-        @Bean
-        public NonWorkDayPeriodService nonWorkDayPeriodService(Provider<EntityManager> em) {
-            return new NonWorkDayPeriodServiceImpl(em);
-        }
-
-        @Bean
-        public DaysService daysService(NonWorkDayPeriodService nonWorkDays) {
-            return new DaysServiceImpl(nonWorkDays);
-        }
-
-        @Bean
-        public OppositionReportService oppositionReportService(Provider<EntityManager> em) {
-            return new OppositionReportServiceImpl(null, null, null, null);
-        }
-
-        @Bean
-        public DaisyAPI daisyAPI(
-                @Value("${daisy.api.url}") String baseUrl,
-                @Value("${daisy.api.username}") String username,
-                @Value("${daisy.api.password}") String password)
-        {
-            return new DaisyAPIImpl(baseUrl, username, password);
-        }
-    }
-}
diff --git a/view/src/main/java/DatabaseMigration.java b/view/src/main/java/DatabaseMigration.java
deleted file mode 100644
index 442e3003e4..0000000000
--- a/view/src/main/java/DatabaseMigration.java
+++ /dev/null
@@ -1,35 +0,0 @@
-import org.flywaydb.core.Flyway;
-
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-import jakarta.servlet.ServletContextEvent;
-import jakarta.servlet.ServletContextListener;
-import javax.sql.DataSource;
-
-public class DatabaseMigration implements ServletContextListener {
-    @Override
-    public void contextInitialized(ServletContextEvent sce) {
-        migrateDatabase();
-    }
-
-    private void migrateDatabase() {
-        Flyway flyway = Flyway.configure()
-                .dataSource(getDataSource())
-                .baselineOnMigrate(true)
-                .table("schema_version")
-                .load();
-        flyway.migrate();
-    }
-
-    private DataSource getDataSource() {
-        try {
-            return InitialContext.doLookup("java:comp/env/jdbc/sciproDS");
-        } catch (NamingException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Override
-    public void contextDestroyed(ServletContextEvent sce) {
-    }
-}
diff --git a/view/src/main/java/se/su/dsv/scipro/FileSystemStore.java b/view/src/main/java/se/su/dsv/scipro/FileSystemStore.java
index 7e80019776..3d0eafc255 100644
--- a/view/src/main/java/se/su/dsv/scipro/FileSystemStore.java
+++ b/view/src/main/java/se/su/dsv/scipro/FileSystemStore.java
@@ -12,7 +12,7 @@ import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.StandardOpenOption;
 
-class FileSystemStore implements FileStore {
+public class FileSystemStore implements FileStore {
     private static final int FILES_PER_SUBDIRECTORY = 1000;
     private static final String FILE_ROOT = "/scipro-files";
 
diff --git a/war/pom.xml b/war/pom.xml
new file mode 100644
index 0000000000..9e5e84b4a6
--- /dev/null
+++ b/war/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>se.su.dsv.scipro</groupId>
+        <artifactId>SciPro</artifactId>
+        <version>0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>war</artifactId>
+    <packaging>war</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <!-- exclude default logging implementation (logback) since we want log4j2 -->
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-log4j2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-tomcat</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-orm</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.wicket</groupId>
+            <artifactId>wicket-spring</artifactId>
+        </dependency>
+
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework</groupId>-->
+        <!--            <artifactId>spring-web</artifactId>-->
+        <!--        </dependency>-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework</groupId>-->
+        <!--            <artifactId>spring-tx</artifactId>-->
+        <!--        </dependency>-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework</groupId>-->
+        <!--            <artifactId>spring-webmvc</artifactId>-->
+        <!--        </dependency>-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework.security</groupId>-->
+        <!--            <artifactId>spring-security-web</artifactId>-->
+        <!--        </dependency>-->
+        <!--        <dependency>-->
+        <!--            <groupId>org.springframework.security</groupId>-->
+        <!--            <artifactId>spring-security-config</artifactId>-->
+        <!--        </dependency>-->
+
+        <dependency>
+            <groupId>se.su.dsv.scipro</groupId>
+            <artifactId>view</artifactId>
+            <version>${project.version}</version>
+            <type>war</type>
+        </dependency>
+        <dependency>
+            <!-- needed only at compile time -->
+            <!-- the classes will be moved as part of the overlay by the war plugin -->
+            <groupId>se.su.dsv.scipro</groupId>
+            <artifactId>view</artifactId>
+            <version>${project.version}</version>
+            <classifier>classes</classifier>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>se.su.dsv.scipro</groupId>
+            <artifactId>api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <version>3.4.0</version>
+                <configuration>
+                    <overlays>
+                        <overlay>
+                            <groupId>se.su.dsv.scipro</groupId>
+                            <artifactId>view</artifactId>
+                            <type>war</type>
+                        </overlay>
+                    </overlays>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/war/src/main/java/se/su/dsv/scipro/war/ApiConfig.java b/war/src/main/java/se/su/dsv/scipro/war/ApiConfig.java
new file mode 100644
index 0000000000..efef48e1c5
--- /dev/null
+++ b/war/src/main/java/se/su/dsv/scipro/war/ApiConfig.java
@@ -0,0 +1,32 @@
+package se.su.dsv.scipro.war;
+
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
+import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
+
+@Configuration
+@ComponentScan("se.su.dsv.scipro.api")
+public class ApiConfig {
+    @Bean
+    public SecurityFilterChain apiSecurity(
+            HttpSecurity http,
+            HandlerMappingIntrospector introspector,
+            WebMvcProperties webMvcProperties)
+            throws Exception
+    {
+        MvcRequestMatcher.Builder mvc = new MvcRequestMatcher.Builder(introspector)
+                .servletPath(webMvcProperties.getServlet().getPath());
+        return http
+                .securityMatcher(mvc.pattern("/**"))
+                .authorizeHttpRequests(authorize -> authorize
+                        .anyRequest().authenticated())
+                .httpBasic(Customizer.withDefaults())
+                .build();
+    }
+}
diff --git a/war/src/main/java/se/su/dsv/scipro/war/CoreConfig.java b/war/src/main/java/se/su/dsv/scipro/war/CoreConfig.java
new file mode 100644
index 0000000000..360419ac35
--- /dev/null
+++ b/war/src/main/java/se/su/dsv/scipro/war/CoreConfig.java
@@ -0,0 +1,98 @@
+package se.su.dsv.scipro.war;
+
+import com.google.common.eventbus.EventBus;
+import jakarta.inject.Provider;
+import jakarta.persistence.EntityManager;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import se.su.dsv.scipro.FileSystemStore;
+import se.su.dsv.scipro.daisyExternal.http.DaisyAPIImpl;
+import se.su.dsv.scipro.file.FileStore;
+import se.su.dsv.scipro.forum.AbstractThreadRepository;
+import se.su.dsv.scipro.forum.AbstractThreadRepositoryImpl;
+import se.su.dsv.scipro.grading.GradingHistory;
+import se.su.dsv.scipro.grading.GradingHistoryEventRepository;
+import se.su.dsv.scipro.grading.GradingServiceImpl;
+import se.su.dsv.scipro.oauth.OAuthSettings;
+import se.su.dsv.scipro.sukat.LDAP;
+import se.su.dsv.scipro.sukat.Sukat;
+
+import java.time.Clock;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+
+@Configuration
+@ComponentScan(
+        basePackages = "se.su.dsv.scipro",
+        includeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Impl$"),
+        excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = {
+                ".*Abstract.*",
+                ".*[Dd]aisy.*",
+                ".*GradingServiceImpl"
+        }))
+public class CoreConfig {
+    private static final int NUMBER_OF_WORKER_THREADS = 4;
+
+    @Bean
+    public ScheduledExecutorService scheduledExecutorService() {
+        return Executors.newScheduledThreadPool(NUMBER_OF_WORKER_THREADS);
+    }
+
+    @Bean
+    public EventBus eventBus() {
+        return new EventBus();
+    }
+
+    @Bean
+    public FileStore fileStore() {
+        return new FileSystemStore();
+    }
+
+    @Bean
+    public Clock clock() {
+        return Clock.systemDefaultZone();
+    }
+
+    @Bean
+    public GradingHistory gradingHistory(GradingHistoryEventRepository gradingHistoryEventRepository) {
+        return new GradingHistory(gradingHistoryEventRepository);
+    }
+
+    @Bean
+    public OAuthSettings oAuthSettings(
+            @Value("${oauth.uri}") String uri,
+            @Value("${oauth.redirectUri}") String redirectUri,
+            @Value("${oauth.clientId}") String clientId,
+            @Value("${oauth.clientSecret}") String clientSecret)
+    {
+        return new OAuthSettings(uri, redirectUri, clientId, clientSecret);
+    }
+
+    @Bean
+    // weird name, both abstract and impl
+    public AbstractThreadRepository abstractThreadRepository(Provider<EntityManager> em) {
+        return new AbstractThreadRepositoryImpl(em);
+    }
+
+    @Bean
+    public Sukat sukat() {
+        return new LDAP();
+    }
+
+    @Bean
+    public DaisyAPIImpl daisyAPI(
+            @Value("${daisy.api.url}") final String baseUrl,
+            @Value("${daisy.api.username}") final String user,
+            @Value("${daisy.api.password}") final String password)
+    {
+        return new DaisyAPIImpl(baseUrl, user, password);
+    }
+
+    @Bean
+    public GradingServiceImpl gradingService(@Value("${service.grading.url}") final String url) {
+        return new GradingServiceImpl(url);
+    }
+}
diff --git a/war/src/main/java/se/su/dsv/scipro/war/Main.java b/war/src/main/java/se/su/dsv/scipro/war/Main.java
new file mode 100644
index 0000000000..71a5c80da3
--- /dev/null
+++ b/war/src/main/java/se/su/dsv/scipro/war/Main.java
@@ -0,0 +1,129 @@
+package se.su.dsv.scipro.war;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.EntityManagerFactory;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.protocol.http.WicketFilter;
+import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+import org.springframework.orm.jpa.SharedEntityManagerCreator;
+import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
+import se.su.dsv.scipro.SciProApplication;
+import se.su.dsv.scipro.profiles.CurrentProfile;
+import se.su.dsv.scipro.system.AggregateUserSearch;
+import se.su.dsv.scipro.system.ResearchArea;
+import se.su.dsv.scipro.system.User;
+import se.su.dsv.scipro.system.UserImportService;
+import se.su.dsv.scipro.system.UserSearchProvider;
+import se.su.dsv.scipro.system.UserSearchService;
+import se.su.dsv.scipro.system.UserService;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+@SpringBootApplication
+@EntityScan("se.su.dsv.scipro")
+@Import({CoreConfig.class, ApiConfig.class})
+public class Main extends SpringBootServletInitializer {
+    @Override
+    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
+        return builder.sources(Main.class);
+    }
+
+    @Bean
+    public EntityManagerFactoryBuilderCustomizer entityManagerFactoryCustomizer() {
+        return factoryBuilder -> {
+            factoryBuilder.setBootstrapExecutor(new SimpleAsyncTaskExecutor());
+        };
+    }
+
+    @Bean
+    public OpenEntityManagerInViewFilter openEntityManagerInViewFilter() {
+        return new OpenEntityManagerInViewFilter();
+    }
+
+    @Bean
+    public FilterRegistrationBean<WicketFilter> wicket(
+            WebApplication webApplication,
+            ApplicationContext applicationContext)
+    {
+        webApplication.getComponentInstantiationListeners()
+                .add(new SpringComponentInjector(webApplication, applicationContext));
+
+        WicketFilter filter = new WicketFilter(webApplication);
+        filter.setFilterPath("");
+
+        FilterRegistrationBean<WicketFilter> registration = new FilterRegistrationBean<>();
+        registration.setFilter(filter);
+        registration.addUrlPatterns("/*");
+        registration.setMatchAfter(true);
+        return registration;
+    }
+
+    @Bean
+    public WebApplication webApplication(CurrentProfile currentProfile) {
+        return new SciProApplication(currentProfile);
+    }
+
+    @Bean
+    public CurrentProfile currentProfile(@Value("${profile}" ) String profile) {
+        CurrentProfile currentProfile = new CurrentProfile();
+        currentProfile.setCurrentProfileString(profile);
+        return currentProfile;
+    }
+
+    /**
+     * Allow injecting of {@link EntityManager} directly instead of {@link EntityManagerFactory}
+     */
+    @Bean
+    public EntityManager entityManager(EntityManagerFactory emf) {
+        return SharedEntityManagerCreator.createSharedEntityManager(emf);
+    }
+
+    /**
+     * Exists because Spring refuses to inject a collection of beans if there are no beans of that type.
+     */
+    @Bean
+    public UserImportService dummyUserImportService() {
+        return new UserImportService() {
+            @Override
+            public Optional<User> importUser(String userName) {
+                return Optional.empty();
+            }
+
+            @Override
+            public List<ImportableUser> search(String searchTerm) {
+                return List.of();
+            }
+
+            @Override
+            public void importUser(ImportableUser importableUser) {
+                // do nothing
+            }
+
+            @Override
+            public Set<ResearchArea> importResearchAreasForSupervisor(User supervisor) {
+                return Set.of();
+            }
+        };
+    }
+
+    @Bean
+    public UserSearchService aggregateSearchService(
+            Set<UserSearchProvider> userSearchProviders,
+            UserService userService)
+    {
+        return new AggregateUserSearch(userSearchProviders, userService);
+    }
+}
diff --git a/war/src/main/resources/application.properties b/war/src/main/resources/application.properties
new file mode 100644
index 0000000000..24eac8b08f
--- /dev/null
+++ b/war/src/main/resources/application.properties
@@ -0,0 +1,12 @@
+spring.datasource.jndi-name=java:/comp/env/jdbc/sciproDS
+spring.flyway.table=schema_version
+
+# Spring Boot changes Hibernates default naming strategy to convert camelCase to snake_case
+# We currently rely on the names to be treated as they are in Java, so we need to set it back to the default
+spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
+
+# We also need to set the implicit strategy to be JPA compliant, as we rely on this naming strategy for certain
+# join tables (idea_Keyword vs idea_keyword)
+spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl
+
+spring.mvc.servlet.path=/api
diff --git a/view/src/main/webapp/WEB-INF/web.xml b/war/src/main/webapp/WEB-INF/web.xml
similarity index 76%
rename from view/src/main/webapp/WEB-INF/web.xml
rename to war/src/main/webapp/WEB-INF/web.xml
index c74f86a197..d78229ac6f 100755
--- a/view/src/main/webapp/WEB-INF/web.xml
+++ b/war/src/main/webapp/WEB-INF/web.xml
@@ -6,11 +6,7 @@
     <display-name>SciPro</display-name>
 
     <listener>
-        <listener-class>ApplicationBootstrap</listener-class>
-    </listener>
-
-    <listener>
-        <listener-class>DatabaseMigration</listener-class>
+        <listener-class>org.springframework.web.SpringServletContainerInitializer</listener-class>
     </listener>
 
     <session-config>