Add backend/frontend scaffolding with Oracle ADB wallet config
- Backend: Spring Boot 3 + WebFlux, JWT auth, Oracle ADB wallet, 8 controllers/services/repositories (Auth~Tag), DTOs, exception handling - Frontend: Next.js 15, TypeScript, Tailwind CSS, AuthContext, 7 pages (dashboard, knowledge, chat, study, todos, habits, login) - DB: V1 migration with 12 tables including VECTOR(1024) + HNSW index - Ops: PM2 ecosystem config, deploy.sh, start-backend.sh - CLAUDE.md: DB credentials replaced with env var references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
129
sundol-backend/pom.xml
Normal file
129
sundol-backend/pom.xml
Normal file
@@ -0,0 +1,129 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.4</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.sundol</groupId>
|
||||
<artifactId>sundol-backend</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>sundol-backend</name>
|
||||
<description>SUNDOL Backend</description>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot WebFlux (Reactive) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot JDBC -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Oracle JDBC -->
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc11</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ucp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.security</groupId>
|
||||
<artifactId>oraclepki</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.security</groupId>
|
||||
<artifactId>osdt_cert</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.security</groupId>
|
||||
<artifactId>osdt_core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.12.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.12.6</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.12.6</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Jsoup -->
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.18.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<mainClass>com.sundol.SundolApplication</mainClass>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.sundol;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
public class SundolApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SundolApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.sundol.config;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
public class DatabaseConfig {
|
||||
|
||||
@Value("${oracle.wallet-path}")
|
||||
private String walletPath;
|
||||
|
||||
@Value("${oracle.tns-name}")
|
||||
private String tnsName;
|
||||
|
||||
@Value("${oracle.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${oracle.password}")
|
||||
private String password;
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setDriverClassName("oracle.jdbc.OracleDriver");
|
||||
config.setJdbcUrl("jdbc:oracle:thin:@" + tnsName + "?TNS_ADMIN=" + walletPath);
|
||||
config.setUsername(username);
|
||||
config.setPassword(password);
|
||||
config.setMaximumPoolSize(10);
|
||||
config.setMinimumIdle(2);
|
||||
config.setConnectionTimeout(30000);
|
||||
config.setIdleTimeout(600000);
|
||||
config.setMaxLifetime(1800000);
|
||||
return new HikariDataSource(config);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
|
||||
return new JdbcTemplate(dataSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.sundol.config;
|
||||
|
||||
import com.sundol.security.JwtAuthenticationFilter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Value("${cors.origin}")
|
||||
private String corsOrigin;
|
||||
|
||||
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
|
||||
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
|
||||
return http
|
||||
.csrf(ServerHttpSecurity.CsrfSpec::disable)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.authorizeExchange(exchanges -> exchanges
|
||||
.pathMatchers("/api/auth/**").permitAll()
|
||||
.pathMatchers(HttpMethod.OPTIONS).permitAll()
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
|
||||
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
|
||||
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowedOrigins(List.of(corsOrigin));
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
|
||||
config.setAllowedHeaders(List.of("*"));
|
||||
config.setAllowCredentials(true);
|
||||
config.setMaxAge(3600L);
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.sundol.controller;
|
||||
|
||||
import com.sundol.dto.LoginRequest;
|
||||
import com.sundol.dto.LoginResponse;
|
||||
import com.sundol.dto.RefreshRequest;
|
||||
import com.sundol.service.AuthService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
public AuthController(AuthService authService) {
|
||||
this.authService = authService;
|
||||
}
|
||||
|
||||
@PostMapping("/google")
|
||||
public Mono<ResponseEntity<LoginResponse>> googleLogin(@RequestBody LoginRequest request) {
|
||||
return authService.googleLogin(request.idToken())
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
public Mono<ResponseEntity<LoginResponse>> refresh(@RequestBody RefreshRequest request) {
|
||||
return authService.refresh(request.refreshToken())
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public Mono<ResponseEntity<Void>> logout(@RequestAttribute("userId") String userId) {
|
||||
return authService.logout(userId)
|
||||
.then(Mono.just(ResponseEntity.ok().<Void>build()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.sundol.controller;
|
||||
|
||||
import com.sundol.dto.ChatMessageRequest;
|
||||
import com.sundol.service.ChatService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/chat")
|
||||
public class ChatController {
|
||||
|
||||
private final ChatService chatService;
|
||||
|
||||
public ChatController(ChatService chatService) {
|
||||
this.chatService = chatService;
|
||||
}
|
||||
|
||||
@GetMapping("/sessions")
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> listSessions(
|
||||
@AuthenticationPrincipal String userId) {
|
||||
return chatService.listSessions(userId)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping("/sessions")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> createSession(
|
||||
@AuthenticationPrincipal String userId) {
|
||||
return chatService.createSession(userId)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@GetMapping("/sessions/{id}/messages")
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> getMessages(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return chatService.getMessages(userId, id)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping("/sessions/{id}/messages")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> sendMessage(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id,
|
||||
@RequestBody ChatMessageRequest request) {
|
||||
return chatService.sendMessage(userId, id, request.content())
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@DeleteMapping("/sessions/{id}")
|
||||
public Mono<ResponseEntity<Void>> deleteSession(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return chatService.deleteSession(userId, id)
|
||||
.then(Mono.just(ResponseEntity.ok().<Void>build()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.sundol.controller;
|
||||
|
||||
import com.sundol.dto.HabitRequest;
|
||||
import com.sundol.service.HabitService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/habits")
|
||||
public class HabitController {
|
||||
|
||||
private final HabitService habitService;
|
||||
|
||||
public HabitController(HabitService habitService) {
|
||||
this.habitService = habitService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> list(
|
||||
@AuthenticationPrincipal String userId) {
|
||||
return habitService.list(userId)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Mono<ResponseEntity<Map<String, Object>>> create(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestBody HabitRequest request) {
|
||||
return habitService.create(userId, request)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> update(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, Object> updates) {
|
||||
return habitService.update(userId, id, updates)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Mono<ResponseEntity<Void>> delete(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return habitService.delete(userId, id)
|
||||
.then(Mono.just(ResponseEntity.ok().<Void>build()));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/checkin")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> checkin(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id,
|
||||
@RequestBody(required = false) Map<String, String> body) {
|
||||
String note = body != null ? body.get("note") : null;
|
||||
return habitService.checkin(userId, id, note)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/logs")
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> getLogs(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id,
|
||||
@RequestParam(required = false) String from,
|
||||
@RequestParam(required = false) String to) {
|
||||
return habitService.getLogs(userId, id, from, to)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.sundol.controller;
|
||||
|
||||
import com.sundol.dto.IngestRequest;
|
||||
import com.sundol.service.KnowledgeService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/knowledge")
|
||||
public class KnowledgeController {
|
||||
|
||||
private final KnowledgeService knowledgeService;
|
||||
|
||||
public KnowledgeController(KnowledgeService knowledgeService) {
|
||||
this.knowledgeService = knowledgeService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> list(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestParam(required = false) String type,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(required = false) String tag,
|
||||
@RequestParam(required = false) String search) {
|
||||
return knowledgeService.list(userId, type, status, tag, search)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping("/ingest")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> ingest(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestBody IngestRequest request) {
|
||||
return knowledgeService.ingest(userId, request)
|
||||
.map(result -> ResponseEntity.status(HttpStatus.ACCEPTED).body(result));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> getById(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return knowledgeService.getById(userId, id)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> update(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, Object> updates) {
|
||||
return knowledgeService.update(userId, id, updates)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Mono<ResponseEntity<Void>> delete(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return knowledgeService.delete(userId, id)
|
||||
.then(Mono.just(ResponseEntity.ok().<Void>build()));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/chunks")
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> getChunks(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return knowledgeService.getChunks(userId, id)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.sundol.controller;
|
||||
|
||||
import com.sundol.service.SearchService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/search")
|
||||
public class SearchController {
|
||||
|
||||
private final SearchService searchService;
|
||||
|
||||
public SearchController(SearchService searchService) {
|
||||
this.searchService = searchService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> search(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestParam String q,
|
||||
@RequestParam(defaultValue = "5") int topK) {
|
||||
return searchService.search(userId, q, topK)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.sundol.controller;
|
||||
|
||||
import com.sundol.dto.StudyCardReviewRequest;
|
||||
import com.sundol.service.StudyCardService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/study-cards")
|
||||
public class StudyCardController {
|
||||
|
||||
private final StudyCardService studyCardService;
|
||||
|
||||
public StudyCardController(StudyCardService studyCardService) {
|
||||
this.studyCardService = studyCardService;
|
||||
}
|
||||
|
||||
@GetMapping("/due")
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> getDueCards(
|
||||
@AuthenticationPrincipal String userId) {
|
||||
return studyCardService.getDueCards(userId)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> getByKnowledgeItem(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestParam String knowledgeItemId) {
|
||||
return studyCardService.getByKnowledgeItem(userId, knowledgeItemId)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping("/generate/{knowledgeItemId}")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> generate(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String knowledgeItemId) {
|
||||
return studyCardService.generate(userId, knowledgeItemId)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/review")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> review(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id,
|
||||
@RequestBody StudyCardReviewRequest request) {
|
||||
return studyCardService.review(userId, id, request.rating())
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.sundol.controller;
|
||||
|
||||
import com.sundol.dto.TagRequest;
|
||||
import com.sundol.service.TagService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/tags")
|
||||
public class TagController {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
public TagController(TagService tagService) {
|
||||
this.tagService = tagService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> list(
|
||||
@AuthenticationPrincipal String userId) {
|
||||
return tagService.list(userId)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Mono<ResponseEntity<Map<String, Object>>> create(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestBody TagRequest request) {
|
||||
return tagService.create(userId, request)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> update(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id,
|
||||
@RequestBody TagRequest request) {
|
||||
return tagService.update(userId, id, request)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Mono<ResponseEntity<Void>> delete(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return tagService.delete(userId, id)
|
||||
.then(Mono.just(ResponseEntity.ok().<Void>build()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.sundol.controller;
|
||||
|
||||
import com.sundol.dto.TodoRequest;
|
||||
import com.sundol.service.TodoService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/todos")
|
||||
public class TodoController {
|
||||
|
||||
private final TodoService todoService;
|
||||
|
||||
public TodoController(TodoService todoService) {
|
||||
this.todoService = todoService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> list(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(required = false) String priority,
|
||||
@RequestParam(required = false) String dueDate) {
|
||||
return todoService.list(userId, status, priority, dueDate)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Mono<ResponseEntity<Map<String, Object>>> create(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@RequestBody TodoRequest request) {
|
||||
return todoService.create(userId, request)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}")
|
||||
public Mono<ResponseEntity<Map<String, Object>>> update(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, Object> updates) {
|
||||
return todoService.update(userId, id, updates)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public Mono<ResponseEntity<Void>> delete(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return todoService.delete(userId, id)
|
||||
.then(Mono.just(ResponseEntity.ok().<Void>build()));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/subtasks")
|
||||
public Mono<ResponseEntity<List<Map<String, Object>>>> getSubtasks(
|
||||
@AuthenticationPrincipal String userId,
|
||||
@PathVariable String id) {
|
||||
return todoService.getSubtasks(userId, id)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record ChatMessageRequest(String content) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record HabitRequest(String name, String description, String habitType, String targetDays, String color) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record IngestRequest(String type, String url, String title, String rawText) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record LoginRequest(String idToken) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record LoginResponse(String accessToken, String refreshToken) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record RefreshRequest(String refreshToken) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record StudyCardReviewRequest(int rating) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record TagRequest(String name, String color) {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.sundol.dto;
|
||||
|
||||
public record TodoRequest(String title, String description, String priority, String dueDate, String parentId) {}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class AppException extends RuntimeException {
|
||||
private final HttpStatus status;
|
||||
|
||||
public AppException(HttpStatus status, String message) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public HttpStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.sundol.exception;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
@Order(-2)
|
||||
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
HttpStatus status;
|
||||
String message;
|
||||
|
||||
if (ex instanceof AppException appEx) {
|
||||
status = appEx.getStatus();
|
||||
message = appEx.getMessage();
|
||||
} else {
|
||||
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
message = "Internal server error";
|
||||
log.error("Unhandled exception", ex);
|
||||
}
|
||||
|
||||
exchange.getResponse().setStatusCode(status);
|
||||
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
String body = "{\"error\":\"" + message.replace("\"", "\\\"") + "\"}";
|
||||
DataBuffer buffer = exchange.getResponse().bufferFactory()
|
||||
.wrap(body.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
return exchange.getResponse().writeWith(Mono.just(buffer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.repository;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class ChatRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public ChatRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// TODO: CRUD for chat_sessions, chat_messages
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.repository;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class HabitRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public HabitRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// TODO: CRUD for habits, habit_logs
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.repository;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class KnowledgeChunkRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public KnowledgeChunkRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// TODO: CRUD for knowledge_chunks, VECTOR_DISTANCE search
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.repository;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class KnowledgeRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public KnowledgeRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// TODO: CRUD for knowledge_items table
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.repository;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class StudyCardRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public StudyCardRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// TODO: CRUD for study_cards, SM-2 queries
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.repository;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class TagRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public TagRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// TODO: CRUD for tags, knowledge_item_tags
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.repository;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class TodoRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public TodoRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// TODO: CRUD for todos table
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sundol.repository;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class UserRepository {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
public UserRepository(JdbcTemplate jdbcTemplate) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
}
|
||||
|
||||
// TODO: findByGoogleSub, upsert, updateRefreshToken
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.sundol.security;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter implements WebFilter {
|
||||
|
||||
private final JwtProvider jwtProvider;
|
||||
|
||||
public JwtAuthenticationFilter(JwtProvider jwtProvider) {
|
||||
this.jwtProvider = jwtProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
String token = extractToken(exchange.getRequest());
|
||||
|
||||
if (token != null && jwtProvider.validateToken(token)) {
|
||||
Claims claims = jwtProvider.parseToken(token);
|
||||
String type = claims.get("type", String.class);
|
||||
|
||||
if ("ACCESS".equals(type)) {
|
||||
var auth = new UsernamePasswordAuthenticationToken(
|
||||
claims.getSubject(),
|
||||
null,
|
||||
List.of(new SimpleGrantedAuthority("ROLE_USER"))
|
||||
);
|
||||
return chain.filter(exchange)
|
||||
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(auth));
|
||||
}
|
||||
}
|
||||
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
private String extractToken(ServerHttpRequest request) {
|
||||
String bearer = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
if (bearer != null && bearer.startsWith("Bearer ")) {
|
||||
return bearer.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.sundol.security;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class JwtProvider {
|
||||
|
||||
private final SecretKey key;
|
||||
private final long accessTokenExpiry;
|
||||
private final long refreshTokenExpiry;
|
||||
|
||||
public JwtProvider(
|
||||
@Value("${jwt.secret}") String secret,
|
||||
@Value("${jwt.access-token-expiry}") long accessTokenExpiry,
|
||||
@Value("${jwt.refresh-token-expiry}") long refreshTokenExpiry) {
|
||||
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
this.accessTokenExpiry = accessTokenExpiry;
|
||||
this.refreshTokenExpiry = refreshTokenExpiry;
|
||||
}
|
||||
|
||||
public String createAccessToken(String userId, String email) {
|
||||
return createToken(userId, email, "ACCESS", accessTokenExpiry);
|
||||
}
|
||||
|
||||
public String createRefreshToken(String userId, String email) {
|
||||
return createToken(userId, email, "REFRESH", refreshTokenExpiry);
|
||||
}
|
||||
|
||||
private String createToken(String userId, String email, String type, long expiry) {
|
||||
Date now = new Date();
|
||||
return Jwts.builder()
|
||||
.subject(userId)
|
||||
.claims(Map.of("email", email, "type", type))
|
||||
.issuedAt(now)
|
||||
.expiration(new Date(now.getTime() + expiry))
|
||||
.signWith(key)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Claims parseToken(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(key)
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
parseToken(token);
|
||||
return true;
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.sundol.service;
|
||||
|
||||
import com.sundol.dto.LoginResponse;
|
||||
import com.sundol.repository.UserRepository;
|
||||
import com.sundol.security.JwtProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final JwtProvider jwtProvider;
|
||||
|
||||
public AuthService(UserRepository userRepository, JwtProvider jwtProvider) {
|
||||
this.userRepository = userRepository;
|
||||
this.jwtProvider = jwtProvider;
|
||||
}
|
||||
|
||||
public Mono<LoginResponse> googleLogin(String idToken) {
|
||||
// TODO: Verify Google ID token, upsert user, issue JWT pair
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<LoginResponse> refresh(String refreshToken) {
|
||||
// TODO: Validate refresh token, issue new token pair
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Void> logout(String userId) {
|
||||
// TODO: Invalidate refresh token
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.sundol.service;
|
||||
|
||||
import com.sundol.repository.ChatRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class ChatService {
|
||||
|
||||
private final ChatRepository chatRepository;
|
||||
|
||||
public ChatService(ChatRepository chatRepository) {
|
||||
this.chatRepository = chatRepository;
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> listSessions(String userId) {
|
||||
// TODO: List chat sessions for user
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> createSession(String userId) {
|
||||
// TODO: Create new chat session
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> getMessages(String userId, String sessionId) {
|
||||
// TODO: Get messages for chat session
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> sendMessage(String userId, String sessionId, String content) {
|
||||
// TODO: RAG pipeline - embed query, search, build prompt, call OCI GenAI
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Void> deleteSession(String userId, String sessionId) {
|
||||
// TODO: Delete chat session and messages
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.sundol.service;
|
||||
|
||||
import com.sundol.dto.HabitRequest;
|
||||
import com.sundol.repository.HabitRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class HabitService {
|
||||
|
||||
private final HabitRepository habitRepository;
|
||||
|
||||
public HabitService(HabitRepository habitRepository) {
|
||||
this.habitRepository = habitRepository;
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> list(String userId) {
|
||||
// TODO: List habits for user
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> create(String userId, HabitRequest request) {
|
||||
// TODO: Create habit
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> update(String userId, String id, Map<String, Object> updates) {
|
||||
// TODO: Update habit
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Void> delete(String userId, String id) {
|
||||
// TODO: Delete habit and logs
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> checkin(String userId, String id, String note) {
|
||||
// TODO: Check in for today
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> getLogs(String userId, String id, String from, String to) {
|
||||
// TODO: Get habit logs
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.sundol.service;
|
||||
|
||||
import com.sundol.dto.IngestRequest;
|
||||
import com.sundol.repository.KnowledgeRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class KnowledgeService {
|
||||
|
||||
private final KnowledgeRepository knowledgeRepository;
|
||||
|
||||
public KnowledgeService(KnowledgeRepository knowledgeRepository) {
|
||||
this.knowledgeRepository = knowledgeRepository;
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> list(String userId, String type, String status, String tag, String search) {
|
||||
// TODO: Query knowledge items with filters
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> ingest(String userId, IngestRequest request) {
|
||||
// TODO: Create knowledge item, trigger async ingest pipeline
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> getById(String userId, String id) {
|
||||
// TODO: Get knowledge item by ID
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> update(String userId, String id, Map<String, Object> updates) {
|
||||
// TODO: Update knowledge item fields
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Void> delete(String userId, String id) {
|
||||
// TODO: Delete knowledge item and all chunks
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> getChunks(String userId, String id) {
|
||||
// TODO: List chunks for knowledge item
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.sundol.service;
|
||||
|
||||
import com.sundol.repository.KnowledgeChunkRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class SearchService {
|
||||
|
||||
private final KnowledgeChunkRepository chunkRepository;
|
||||
|
||||
public SearchService(KnowledgeChunkRepository chunkRepository) {
|
||||
this.chunkRepository = chunkRepository;
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> search(String userId, String query, int topK) {
|
||||
// TODO: Embed query via OCI GenAI, VECTOR_DISTANCE search
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.sundol.service;
|
||||
|
||||
import com.sundol.repository.StudyCardRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class StudyCardService {
|
||||
|
||||
private final StudyCardRepository studyCardRepository;
|
||||
|
||||
public StudyCardService(StudyCardRepository studyCardRepository) {
|
||||
this.studyCardRepository = studyCardRepository;
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> getDueCards(String userId) {
|
||||
// TODO: Get cards due for review today
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> getByKnowledgeItem(String userId, String knowledgeItemId) {
|
||||
// TODO: Get cards for a specific knowledge item
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> generate(String userId, String knowledgeItemId) {
|
||||
// TODO: Trigger AI card generation from knowledge item
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> review(String userId, String id, int rating) {
|
||||
// TODO: Apply SM-2 algorithm and update card
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.sundol.service;
|
||||
|
||||
import com.sundol.dto.TagRequest;
|
||||
import com.sundol.repository.TagRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class TagService {
|
||||
|
||||
private final TagRepository tagRepository;
|
||||
|
||||
public TagService(TagRepository tagRepository) {
|
||||
this.tagRepository = tagRepository;
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> list(String userId) {
|
||||
// TODO: List tags for user
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> create(String userId, TagRequest request) {
|
||||
// TODO: Create tag
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> update(String userId, String id, TagRequest request) {
|
||||
// TODO: Update tag
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Void> delete(String userId, String id) {
|
||||
// TODO: Delete tag and remove from items
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.sundol.service;
|
||||
|
||||
import com.sundol.dto.TodoRequest;
|
||||
import com.sundol.repository.TodoRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class TodoService {
|
||||
|
||||
private final TodoRepository todoRepository;
|
||||
|
||||
public TodoService(TodoRepository todoRepository) {
|
||||
this.todoRepository = todoRepository;
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> list(String userId, String status, String priority, String dueDate) {
|
||||
// TODO: List todos with filters
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> create(String userId, TodoRequest request) {
|
||||
// TODO: Create todo
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Map<String, Object>> update(String userId, String id, Map<String, Object> updates) {
|
||||
// TODO: Update todo fields
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<Void> delete(String userId, String id) {
|
||||
// TODO: Delete todo and subtasks
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
|
||||
public Mono<List<Map<String, Object>>> getSubtasks(String userId, String id) {
|
||||
// TODO: List subtasks for todo
|
||||
return Mono.error(new UnsupportedOperationException("Not implemented yet"));
|
||||
}
|
||||
}
|
||||
24
sundol-backend/src/main/resources/application.yml
Normal file
24
sundol-backend/src/main/resources/application.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
server:
|
||||
port: ${SERVER_PORT:8080}
|
||||
|
||||
oracle:
|
||||
wallet-path: ${ORACLE_WALLET_PATH}
|
||||
tns-name: ${ORACLE_TNS_NAME}
|
||||
username: ${ORACLE_USERNAME}
|
||||
password: ${ORACLE_PASSWORD}
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET}
|
||||
access-token-expiry: ${JWT_ACCESS_TOKEN_EXPIRY:900000}
|
||||
refresh-token-expiry: ${JWT_REFRESH_TOKEN_EXPIRY:604800000}
|
||||
|
||||
cors:
|
||||
origin: ${CORS_ORIGIN:http://localhost:3000}
|
||||
|
||||
oci:
|
||||
compartment-id: ${OCI_COMPARTMENT_ID:}
|
||||
region: ${OCI_REGION:ap-seoul-1}
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.sundol: DEBUG
|
||||
Reference in New Issue
Block a user