From ce9c90c18f7fc58c3838b9922a250d5c88678bd7 Mon Sep 17 00:00:00 2001 From: Periyzat Kenesbaeva <115492286+Periyzat@users.noreply.github.com> Date: Sun, 21 Dec 2025 04:19:51 +0900 Subject: [PATCH 1/7] feat: api for json extraction From 82fb80f418bb9c4424fcea2a1d24f4be2e670a91 Mon Sep 17 00:00:00 2001 From: Periyzat Kenesbaeva <115492286+Periyzat@users.noreply.github.com> Date: Sun, 21 Dec 2025 04:21:07 +0900 Subject: [PATCH 2/7] feat: develop homepage --- apps/web/src/app/homepage/page.tsx | 188 +++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 apps/web/src/app/homepage/page.tsx diff --git a/apps/web/src/app/homepage/page.tsx b/apps/web/src/app/homepage/page.tsx new file mode 100644 index 0000000..1d95b65 --- /dev/null +++ b/apps/web/src/app/homepage/page.tsx @@ -0,0 +1,188 @@ +'use client' + +import { useState } from 'react' +import { useRouter } from 'next/navigation' +import { Upload, CheckCircle, Zap, Clock } from 'lucide-react' +import { analyzeRepository } from './lib/api' + +export default function HomePage() { + const router = useRouter() + const [selectedFile, setSelectedFile] = useState(null) + const [isDragging, setIsDragging] = useState(false) + const [isAnalyzing, setIsAnalyzing] = useState(false) + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (file && file.name.endsWith('.json')) { + setSelectedFile(file) + } else { + alert('Please select a .json file') + setSelectedFile(null) + } + } + + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault() + setIsDragging(true) + } + + const handleDragLeave = () => { + setIsDragging(false) + } + + const handleDrop = (event: React.DragEvent) => { + event.preventDefault() + setIsDragging(false) + + const file = event.dataTransfer.files?.[0] + if (file && file.name.endsWith('.json')) { + setSelectedFile(file) + } else { + alert('Please select a .json file') + setSelectedFile(null) + } + } + + const handleBrowseClick = (event: React.MouseEvent) => { + event.stopPropagation() + document.getElementById('fileInput')?.click() + } + + const handleAnalyze = async () => { + if (!selectedFile) { + alert('Please select a snapshot file first') + return + } + + setIsAnalyzing(true) + + try { + // Send file to Spring Boot backend + const result = await analyzeRepository(selectedFile) + + // Store result so results page can access it + sessionStorage.setItem('analysisResult', JSON.stringify(result)) + + // Navigate to results page + router.push('/results') + + } catch (error) { + console.error('Error:', error) + alert('Failed to analyze repository. Please make sure your Spring Boot backend is running on port 8080.') + } finally { + setIsAnalyzing(false) + } + } + + return ( +
+ {/* Header */} +
+

+ Safe Git Recovery, Step-by-Step +

+

+ Prevent destructive git commands with AI-powered analysis. Upload your + repository snapshot and get a safe, verified recovery plan. +

+
+ + {/* Main Upload Section */} +
+
+ {/* Upload Area */} +
document.getElementById('fileInput')?.click()} + > + + + {/* Upload Icon */} +
+ +
+ +

+ Drop your snapshot.json here +

+

or click to browse files

+ + {selectedFile && ( +

Selected: {selectedFile.name}

+ )} + + +
+ + {/* Analyze Button */} + + + {/* Privacy Notice */} +

+ Your snapshot data is analyzed locally. We never store your git data on our servers. +

+
+ + {/* Feature Cards */} +
+ {/* Safe Commands */} +
+
+ +
+

Safe Commands

+

+ Every step is verified before execution +

+
+ + {/* AI-Powered */} +
+
+ +
+

AI-Powered

+

+ Intelligent analysis of your repo state +

+
+ + {/* Step-by-Step */} +
+
+ +
+

Step-by-Step

+

+ Clear guidance through the recovery +

+
+
+
+
+ ) +} From 8e17d5e61b3600c2801f0afd9f3a91901fbf1165 Mon Sep 17 00:00:00 2001 From: Periyzat Kenesbaeva <115492286+Periyzat@users.noreply.github.com> Date: Sun, 21 Dec 2025 04:22:10 +0900 Subject: [PATCH 3/7] feat: develop layout & css of homepage --- apps/web/src/app/homepage/globals.css | 26 +++++++++++++++++++++++++ apps/web/src/app/homepage/layout.tsx | 28 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 apps/web/src/app/homepage/globals.css create mode 100644 apps/web/src/app/homepage/layout.tsx diff --git a/apps/web/src/app/homepage/globals.css b/apps/web/src/app/homepage/globals.css new file mode 100644 index 0000000..a2dc41e --- /dev/null +++ b/apps/web/src/app/homepage/globals.css @@ -0,0 +1,26 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/apps/web/src/app/homepage/layout.tsx b/apps/web/src/app/homepage/layout.tsx new file mode 100644 index 0000000..b90af8d --- /dev/null +++ b/apps/web/src/app/homepage/layout.tsx @@ -0,0 +1,28 @@ +import type { Metadata } from "next"; +import Header from "../components/header"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "GitGuard Agent", + description: "Safe Git Recovery, Step-by-Step", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {/* Header component - Shows on ALL pages */} +
+ + {/* Main content with padding for fixed header */} +
+ {children} +
+ + + ); +} \ No newline at end of file From 632b3b4b2df806cbeec83192294b12935c50d8db Mon Sep 17 00:00:00 2001 From: Azizbek Arzikulov Date: Sun, 21 Dec 2025 05:23:34 +0900 Subject: [PATCH 4/7] refactor: clean up codebase and integrate SpoonOS --- apps/agent/.env.example | 10 + apps/agent/README.md | 81 + apps/agent/main.py | 808 +++++++++ apps/agent/requirements.txt | 7 + apps/cli/src/commands/send.ts | 166 ++ apps/cli/src/index.ts | 8 + apps/scoop-hackathon/.env.example | 45 - apps/scoop-hackathon/.gitignore | 96 - apps/scoop-hackathon/CLEAN_FLYWAY.md | 42 - apps/scoop-hackathon/MYSQL_QUERIES.md | 390 ---- apps/scoop-hackathon/POSTMAN_SIGNUP_GUIDE.md | 256 --- apps/scoop-hackathon/README.md | 203 --- apps/scoop-hackathon/SETUP_MYSQL.md | 77 - .../TROUBLESHOOTING_DATABASE.md | 269 --- apps/scoop-hackathon/fix_flyway.sh | 59 - apps/scoop-hackathon/fix_flyway.sql | 20 - apps/scoop-hackathon/pom.xml | 215 --- apps/scoop-hackathon/quick_fix_flyway.sh | 50 - .../hackathon/ScoopHackathonApplication.java | 52 - .../scoop/hackathon/config/DotEnvConfig.java | 17 - .../hackathon/config/EnvironmentConfig.java | 28 - .../config/FlywayRepairComponent.java | 115 -- .../hackathon/config/FlywayRepairConfig.java | 113 -- .../scoop/hackathon/config/OpenApiConfig.java | 60 - .../hackathon/config/SecurityConfig.java | 68 - .../controller/AccountController.java | 72 - .../hackathon/controller/AuthController.java | 183 -- .../AuthenticationHistoryController.java | 87 - .../hackathon/controller/EventController.java | 76 - .../controller/GitSessionController.java | 62 - .../hackathon/controller/PlanController.java | 64 - .../controller/SessionController.java | 77 - .../controller/SnapshotController.java | 64 - .../hackathon/controller/TraceController.java | 70 - .../hackathon/controller/UserController.java | 70 - .../VerificationTokenController.java | 72 - .../com/scoop/hackathon/dto/AccountDto.java | 117 -- .../com/scoop/hackathon/dto/AuthResponse.java | 73 - .../dto/AuthenticationHistoryDto.java | 92 - .../hackathon/dto/CreateAccountRequest.java | 119 -- .../CreateAuthenticationHistoryRequest.java | 84 - .../hackathon/dto/CreateEventRequest.java | 49 - .../dto/CreateGitSessionRequest.java | 44 - .../hackathon/dto/CreatePlanRequest.java | 61 - .../hackathon/dto/CreateSessionRequest.java | 45 - .../hackathon/dto/CreateSnapshotRequest.java | 42 - .../hackathon/dto/CreateTraceRequest.java | 80 - .../hackathon/dto/CreateUserRequest.java | 48 - .../dto/CreateVerificationTokenRequest.java | 45 - .../com/scoop/hackathon/dto/EventDto.java | 65 - .../scoop/hackathon/dto/GitSessionDto.java | 73 - .../com/scoop/hackathon/dto/LoginRequest.java | 39 - .../java/com/scoop/hackathon/dto/PlanDto.java | 74 - .../scoop/hackathon/dto/RegisterRequest.java | 54 - .../com/scoop/hackathon/dto/SessionDto.java | 47 - .../com/scoop/hackathon/dto/SnapshotDto.java | 56 - .../com/scoop/hackathon/dto/TraceDto.java | 92 - .../hackathon/dto/UpdateAccountRequest.java | 108 -- .../UpdateAuthenticationHistoryRequest.java | 63 - .../hackathon/dto/UpdateEventRequest.java | 45 - .../hackathon/dto/UpdatePlanRequest.java | 54 - .../hackathon/dto/UpdateSessionRequest.java | 38 - .../hackathon/dto/UpdateSnapshotRequest.java | 36 - .../hackathon/dto/UpdateTraceRequest.java | 72 - .../hackathon/dto/UpdateUserRequest.java | 39 - .../dto/UpdateVerificationTokenRequest.java | 29 - .../java/com/scoop/hackathon/dto/UserDto.java | 73 - .../hackathon/dto/VerificationTokenDto.java | 38 - .../com/scoop/hackathon/entity/Account.java | 154 -- .../entity/AuthenticationHistory.java | 146 -- .../com/scoop/hackathon/entity/Event.java | 95 - .../scoop/hackathon/entity/GitSession.java | 143 -- .../java/com/scoop/hackathon/entity/Plan.java | 111 -- .../com/scoop/hackathon/entity/Session.java | 66 - .../com/scoop/hackathon/entity/Snapshot.java | 102 -- .../com/scoop/hackathon/entity/Trace.java | 135 -- .../java/com/scoop/hackathon/entity/User.java | 153 -- .../hackathon/entity/VerificationToken.java | 53 - .../exception/BadCredentialsException.java | 8 - .../exception/GlobalExceptionHandler.java | 112 -- .../exception/ResourceNotFoundException.java | 12 - .../scoop/hackathon/payload/ApiResponse.java | 40 - .../repository/AccountRepository.java | 15 - .../AuthenticationHistoryRepository.java | 16 - .../hackathon/repository/EventRepository.java | 15 - .../repository/GitSessionRepository.java | 14 - .../hackathon/repository/PlanRepository.java | 14 - .../repository/SessionRepository.java | 16 - .../repository/SnapshotRepository.java | 14 - .../hackathon/repository/TraceRepository.java | 15 - .../hackathon/repository/UserRepository.java | 14 - .../VerificationTokenRepository.java | 14 - .../security/JwtAuthenticationFilter.java | 76 - .../com/scoop/hackathon/security/JwtUtil.java | 108 -- .../security/UserDetailsServiceImpl.java | 33 - .../hackathon/service/AccountService.java | 18 - .../hackathon/service/AccountServiceImpl.java | 135 -- .../service/AuthenticationHistoryService.java | 20 - .../AuthenticationHistoryServiceImpl.java | 136 -- .../service/AuthenticationService.java | 228 --- .../scoop/hackathon/service/EventService.java | 19 - .../hackathon/service/EventServiceImpl.java | 111 -- .../hackathon/service/GitSessionService.java | 16 - .../service/GitSessionServiceImpl.java | 115 -- .../scoop/hackathon/service/PlanService.java | 17 - .../hackathon/service/PlanServiceImpl.java | 109 -- .../hackathon/service/SessionService.java | 19 - .../hackathon/service/SessionServiceImpl.java | 115 -- .../hackathon/service/SnapshotService.java | 17 - .../service/SnapshotServiceImpl.java | 103 -- .../scoop/hackathon/service/TraceService.java | 18 - .../hackathon/service/TraceServiceImpl.java | 136 -- .../scoop/hackathon/service/UserService.java | 18 - .../hackathon/service/UserServiceImpl.java | 114 -- .../service/VerificationTokenService.java | 18 - .../service/VerificationTokenServiceImpl.java | 92 - .../scoop/hackathon/util/CuidGenerator.java | 12 - .../src/main/resources/application.properties | 131 -- .../db/migration/V1__Initial_schema.sql | 139 -- ...2__Create_authentication_history_table.sql | 22 - apps/web/package.json | 4 +- apps/web/prisma/schema.prisma | 202 ++- .../public/test-snapshots/detached-head.json | 38 + .../public/test-snapshots/merge-conflict.json | 68 + .../test-snapshots/rebase-in-progress.json | 57 + apps/web/src/app/analyzer/page.tsx | 130 -- apps/web/src/app/api/auth/register/route.ts | 22 + apps/web/src/app/api/incident/[id]/route.ts | 95 + .../src/app/api/sessions/[id]/plan/route.ts | 172 +- apps/web/src/app/api/sessions/[id]/route.ts | 93 +- .../src/app/api/sessions/[id]/verify/route.ts | 73 +- apps/web/src/app/api/sessions/route.ts | 37 +- .../web/src/app/api/snapshots/ingest/route.ts | 385 ++++ apps/web/src/app/auth/signin/page.tsx | 89 +- apps/web/src/app/auth/signup/page.tsx | 82 +- apps/web/src/app/components/HistoryPage.tsx | 414 ----- apps/web/src/app/components/ui/badge.tsx | 52 - apps/web/src/app/dashboard/page.tsx | 495 +++++ apps/web/src/app/globals.css | 43 + apps/web/src/app/history/page.tsx | 13 +- apps/web/src/app/home/globals.css | 26 - apps/web/src/app/home/layout.tsx | 28 - apps/web/src/app/home/page.tsx | 188 -- apps/web/src/app/homepage/globals.css | 26 - apps/web/src/app/homepage/layout.tsx | 28 - apps/web/src/app/homepage/page.tsx | 188 -- apps/web/src/app/incident/[id]/page.tsx | 255 +++ .../app/incident/[id]/tabs/ConflictsTab.tsx | 282 +++ .../app/incident/[id]/tabs/OverviewTab.tsx | 198 ++ .../src/app/incident/[id]/tabs/PlanTab.tsx | 311 ++++ .../src/app/incident/[id]/tabs/TraceTab.tsx | 236 +++ .../src/app/incident/[id]/tabs/VerifyTab.tsx | 282 +++ apps/web/src/app/layout.tsx | 5 +- apps/web/src/app/page.tsx | 227 ++- apps/web/src/app/session/[id]/page.module.css | 1589 ++++------------ apps/web/src/app/session/[id]/page.tsx | 1591 ++++++++++++----- apps/web/src/components/Header.tsx | 199 +++ apps/web/src/components/HistoryPage.tsx | 351 ++++ apps/web/src/components/Providers.tsx | 7 + apps/web/src/components/ui/badge.tsx | 53 + apps/web/src/lib/auth.config.ts | 29 +- apps/web/src/lib/auth.ts | 34 + apps/web/src/lib/db.ts | 315 +++- .../src/{app/components/ui => lib}/utils.ts | 0 apps/web/tsconfig.tsbuildinfo | 1 + package.json | 1 - packages/schema/src/analysis.ts | 134 ++ packages/schema/src/index.ts | 14 +- packages/schema/src/plan.ts | 1 + packages/schema/tsconfig.tsbuildinfo | 2 +- pnpm-lock.yaml | 40 +- 171 files changed, 7182 insertions(+), 11741 deletions(-) create mode 100644 apps/agent/.env.example create mode 100644 apps/agent/README.md create mode 100644 apps/agent/main.py create mode 100644 apps/agent/requirements.txt create mode 100644 apps/cli/src/commands/send.ts delete mode 100644 apps/scoop-hackathon/.env.example delete mode 100644 apps/scoop-hackathon/.gitignore delete mode 100644 apps/scoop-hackathon/CLEAN_FLYWAY.md delete mode 100644 apps/scoop-hackathon/MYSQL_QUERIES.md delete mode 100644 apps/scoop-hackathon/POSTMAN_SIGNUP_GUIDE.md delete mode 100644 apps/scoop-hackathon/README.md delete mode 100644 apps/scoop-hackathon/SETUP_MYSQL.md delete mode 100644 apps/scoop-hackathon/TROUBLESHOOTING_DATABASE.md delete mode 100755 apps/scoop-hackathon/fix_flyway.sh delete mode 100644 apps/scoop-hackathon/fix_flyway.sql delete mode 100644 apps/scoop-hackathon/pom.xml delete mode 100755 apps/scoop-hackathon/quick_fix_flyway.sh delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/ScoopHackathonApplication.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/DotEnvConfig.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/EnvironmentConfig.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/FlywayRepairComponent.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/FlywayRepairConfig.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/OpenApiConfig.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/SecurityConfig.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AccountController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AuthController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AuthenticationHistoryController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/EventController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/GitSessionController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/PlanController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/SessionController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/SnapshotController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/TraceController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/UserController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/VerificationTokenController.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AccountDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AuthResponse.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AuthenticationHistoryDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateAccountRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateAuthenticationHistoryRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateEventRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateGitSessionRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreatePlanRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateSessionRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateSnapshotRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateTraceRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateUserRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateVerificationTokenRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/EventDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/GitSessionDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/LoginRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/PlanDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/RegisterRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/SessionDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/SnapshotDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/TraceDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateAccountRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateAuthenticationHistoryRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateEventRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdatePlanRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateSessionRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateSnapshotRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateTraceRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateUserRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateVerificationTokenRequest.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UserDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/VerificationTokenDto.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Account.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/AuthenticationHistory.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Event.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/GitSession.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Plan.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Session.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Snapshot.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Trace.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/User.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/VerificationToken.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/BadCredentialsException.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/GlobalExceptionHandler.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/ResourceNotFoundException.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/payload/ApiResponse.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/AccountRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/AuthenticationHistoryRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/EventRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/GitSessionRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/PlanRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/SessionRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/SnapshotRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/TraceRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/UserRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/VerificationTokenRepository.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/JwtAuthenticationFilter.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/JwtUtil.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/UserDetailsServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AccountService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AccountServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationHistoryService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationHistoryServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/EventService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/EventServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/GitSessionService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/GitSessionServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/PlanService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/PlanServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SessionService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SessionServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SnapshotService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SnapshotServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/TraceService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/TraceServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/UserService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/UserServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/VerificationTokenService.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/VerificationTokenServiceImpl.java delete mode 100644 apps/scoop-hackathon/src/main/java/com/scoop/hackathon/util/CuidGenerator.java delete mode 100644 apps/scoop-hackathon/src/main/resources/application.properties delete mode 100644 apps/scoop-hackathon/src/main/resources/db/migration/V1__Initial_schema.sql delete mode 100644 apps/scoop-hackathon/src/main/resources/db/migration/V2__Create_authentication_history_table.sql create mode 100644 apps/web/public/test-snapshots/detached-head.json create mode 100644 apps/web/public/test-snapshots/merge-conflict.json create mode 100644 apps/web/public/test-snapshots/rebase-in-progress.json delete mode 100644 apps/web/src/app/analyzer/page.tsx create mode 100644 apps/web/src/app/api/incident/[id]/route.ts create mode 100644 apps/web/src/app/api/snapshots/ingest/route.ts delete mode 100644 apps/web/src/app/components/HistoryPage.tsx delete mode 100644 apps/web/src/app/components/ui/badge.tsx create mode 100644 apps/web/src/app/dashboard/page.tsx delete mode 100644 apps/web/src/app/home/globals.css delete mode 100644 apps/web/src/app/home/layout.tsx delete mode 100644 apps/web/src/app/home/page.tsx delete mode 100644 apps/web/src/app/homepage/globals.css delete mode 100644 apps/web/src/app/homepage/layout.tsx delete mode 100644 apps/web/src/app/homepage/page.tsx create mode 100644 apps/web/src/app/incident/[id]/page.tsx create mode 100644 apps/web/src/app/incident/[id]/tabs/ConflictsTab.tsx create mode 100644 apps/web/src/app/incident/[id]/tabs/OverviewTab.tsx create mode 100644 apps/web/src/app/incident/[id]/tabs/PlanTab.tsx create mode 100644 apps/web/src/app/incident/[id]/tabs/TraceTab.tsx create mode 100644 apps/web/src/app/incident/[id]/tabs/VerifyTab.tsx create mode 100644 apps/web/src/components/Header.tsx create mode 100644 apps/web/src/components/HistoryPage.tsx create mode 100644 apps/web/src/components/Providers.tsx create mode 100644 apps/web/src/components/ui/badge.tsx rename apps/web/src/{app/components/ui => lib}/utils.ts (100%) create mode 100644 apps/web/tsconfig.tsbuildinfo create mode 100644 packages/schema/src/analysis.ts diff --git a/apps/agent/.env.example b/apps/agent/.env.example new file mode 100644 index 0000000..fdad842 --- /dev/null +++ b/apps/agent/.env.example @@ -0,0 +1,10 @@ +# Agent Service Configuration +PORT=8000 +HOST=0.0.0.0 + +# Anthropic API Key (for Claude) +ANTHROPIC_API_KEY=your_anthropic_api_key_here + +# Model Configuration +MODEL_NAME=claude-sonnet-4-20250514 +MAX_TOKENS=4096 diff --git a/apps/agent/README.md b/apps/agent/README.md new file mode 100644 index 0000000..4b326ac --- /dev/null +++ b/apps/agent/README.md @@ -0,0 +1,81 @@ +# GitGuard Agent Service + +SpoonOS-powered Git incident analysis service using Claude AI. + +## Setup + +1. Create virtual environment: +```bash +cd apps/agent +python -m venv venv + +# Windows +venv\Scripts\activate + +# macOS/Linux +source venv/bin/activate +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +3. Configure environment: +```bash +cp .env.example .env +# Edit .env and add your ANTHROPIC_API_KEY +``` + +4. Run the service: +```bash +python main.py +# Or with uvicorn for development: +uvicorn main:app --reload --port 8000 +``` + +## API Endpoints + +### POST /analyze +Analyze a git snapshot and return structured analysis. + +**Request:** +```json +{ + "snapshot": { ... }, + "options": { + "includeGraph": true, + "maxConflictFiles": 10, + "maxHunksPerFile": 5 + } +} +``` + +**Response:** +```json +{ + "success": true, + "analysis": { + "issueType": "merge_conflict", + "summary": "...", + "repoGraph": { ... }, + "conflicts": [ ... ], + "plan": [ ... ] + }, + "durationMs": 1234 +} +``` + +### POST /explain/conflict +Get AI explanation for a specific conflict hunk. + +### GET /health +Health check endpoint. + +## Environment Variables + +- `ANTHROPIC_API_KEY` - Your Anthropic API key +- `MODEL_NAME` - Claude model to use (default: claude-sonnet-4-20250514) +- `MAX_TOKENS` - Max tokens for AI responses (default: 4096) +- `PORT` - Server port (default: 8000) +- `HOST` - Server host (default: 0.0.0.0) diff --git a/apps/agent/main.py b/apps/agent/main.py new file mode 100644 index 0000000..5f8c633 --- /dev/null +++ b/apps/agent/main.py @@ -0,0 +1,808 @@ +""" +GitGuard Agent Service - SpoonOS-powered Git Analysis +Uses SpoonOS Graph System for multi-stage AI pipeline +""" +import os +import time +import json +from typing import Optional, TypedDict +from dotenv import load_dotenv +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel + +# SpoonOS imports +from spoon_ai.graph import StateGraph, END +from spoon_ai.chat import ChatBot +from spoon_ai.agents.toolcall import ToolCallAgent +from spoon_ai.tools import ToolManager +from spoon_ai.tools.base import BaseTool + +load_dotenv() + +app = FastAPI( + title="GitGuard Agent", + description="SpoonOS-powered Git incident analysis service with graph-based AI pipeline", + version="2.0.0", +) + +# CORS for web app +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Initialize SpoonOS LLM +MODEL = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514") +llm = ChatBot( + llm_provider="anthropic", + model_name=MODEL, +) + + +# ========================================== +# SpoonOS Graph State Definition +# ========================================== + +class GitAnalysisState(TypedDict): + """State that flows through the SpoonOS graph pipeline.""" + # Input + snapshot: dict + options: dict + + # Stage outputs + issue_type: str + risk_level: str + repo_graph: dict + conflicts: list + signals: dict + + # AI-generated outputs + summary: str + conflict_explanations: dict + plan_steps: list + + # Metadata + stage_traces: list + error: str + + +# ========================================== +# SpoonOS Tools +# ========================================== + +class DetectIssueTool(BaseTool): + """Tool to detect the primary issue type from a git snapshot.""" + name: str = "detect_issue" + description: str = "Analyze git snapshot to detect the primary issue (merge_conflict, detached_head, rebase_in_progress, clean)" + parameters: dict = { + "type": "object", + "properties": { + "snapshot": {"type": "object", "description": "Git repository snapshot"} + }, + "required": ["snapshot"] + } + + async def execute(self, snapshot: dict) -> dict: + unmerged = snapshot.get("unmergedFiles", []) + is_detached = snapshot.get("isDetachedHead", False) + rebase_state = snapshot.get("rebaseState", {}) + + if unmerged and len(unmerged) > 0: + issue_type = "merge_conflict" + risk = "high" if len(unmerged) > 3 else "medium" + elif rebase_state.get("inProgress", False): + issue_type = "rebase_in_progress" + risk = "medium" + elif is_detached: + issue_type = "detached_head" + risk = "medium" + else: + staged = snapshot.get("stagedFiles", []) + modified = snapshot.get("modifiedFiles", []) + if not staged and not modified: + issue_type = "clean" + risk = "low" + else: + issue_type = "unknown" + risk = "low" + + return {"issue_type": issue_type, "risk_level": risk} + + +class BuildGraphTool(BaseTool): + """Tool to build repository visualization graph.""" + name: str = "build_graph" + description: str = "Build a visual graph representation of the repository state" + parameters: dict = { + "type": "object", + "properties": { + "snapshot": {"type": "object", "description": "Git repository snapshot"} + }, + "required": ["snapshot"] + } + + async def execute(self, snapshot: dict) -> dict: + nodes = [] + edges = [] + + branch = snapshot.get("branch", {}) + head_oid = branch.get("oid", "")[:7] + head_name = branch.get("head", "HEAD") + is_detached = snapshot.get("isDetachedHead", False) + + # Add HEAD node + nodes.append({ + "id": "head", + "type": "head", + "label": "HEAD", + "sha": head_oid, + "isCurrent": True, + "isDetached": is_detached, + "x": 400, "y": 50 # Position for visualization + }) + + # Add current branch + if not is_detached and head_name: + nodes.append({ + "id": f"branch-{head_name}", + "type": "branch", + "label": head_name, + "sha": head_oid, + "isCurrent": True, + "x": 550, "y": 50 + }) + edges.append({"from": "head", "to": f"branch-{head_name}", "type": "ref"}) + + # Add commits from log + recent_log = snapshot.get("recentLog", [])[:8] + for i, entry in enumerate(recent_log): + commit_id = f"commit-{i}" + sha = entry.get("hash", "")[:7] + message = entry.get("message", "")[:40] + refs = entry.get("refs", []) + + nodes.append({ + "id": commit_id, + "type": "commit", + "label": message, + "sha": sha, + "isCurrent": (i == 0), + "x": 400, + "y": 120 + (i * 80) + }) + + # Add refs + for j, ref in enumerate(refs): + if ref and not ref.startswith("HEAD"): + ref_id = f"ref-{ref.replace('/', '-')}" + ref_type = "remote" if "/" in ref else "branch" + nodes.append({ + "id": ref_id, + "type": ref_type, + "label": ref, + "sha": sha, + "x": 550 + (j * 100), + "y": 120 + (i * 80) + }) + edges.append({"from": ref_id, "to": commit_id, "type": "ref"}) + + # Connect to previous commit + if i > 0: + edges.append({"from": f"commit-{i-1}", "to": commit_id, "type": "parent"}) + + # Connect HEAD to first commit + if recent_log: + edges.append({"from": "head", "to": "commit-0", "type": "pointer"}) + + # Add merge head if exists + merge_head = snapshot.get("mergeHead") + if merge_head: + nodes.append({ + "id": "merge-head", + "type": "merge", + "label": "MERGE_HEAD", + "sha": merge_head[:7], + "x": 250, "y": 50 + }) + edges.append({"from": "merge-head", "to": "commit-0", "type": "merge"}) + + return { + "nodes": nodes, + "edges": edges, + "headRef": head_oid, + "mergeHeadRef": merge_head[:7] if merge_head else None + } + + +class ExtractConflictsTool(BaseTool): + """Tool to extract and structure conflict information.""" + name: str = "extract_conflicts" + description: str = "Extract conflict files and hunks from snapshot" + parameters: dict = { + "type": "object", + "properties": { + "snapshot": {"type": "object"}, + "max_files": {"type": "integer", "default": 10}, + "max_hunks": {"type": "integer", "default": 5} + }, + "required": ["snapshot"] + } + + async def execute(self, snapshot: dict, max_files: int = 10, max_hunks: int = 5) -> list: + conflicts = [] + unmerged = snapshot.get("unmergedFiles", [])[:max_files] + + for file_data in unmerged: + path = file_data.get("path", "unknown") + blocks = file_data.get("conflictBlocks", [])[:max_hunks] + + hunks = [] + for i, block in enumerate(blocks): + hunks.append({ + "index": i, + "startLine": block.get("startLine"), + "endLine": block.get("endLine"), + "baseText": block.get("context", ""), + "oursText": block.get("oursContent", ""), + "theirsText": block.get("theirsContent", ""), + "linesAdded": len(block.get("oursContent", "").split("\n")), + "linesRemoved": len(block.get("theirsContent", "").split("\n")), + }) + + conflicts.append({ + "path": path, + "hunks": hunks, + "hunkCount": len(hunks), + "severity": "high" if len(hunks) > 2 else "medium" if len(hunks) > 1 else "low" + }) + + return conflicts + + +# ========================================== +# SpoonOS Graph Nodes +# ========================================== + +async def detect_issue_node(state: GitAnalysisState) -> dict: + """Stage 1: Detect issue type and risk level.""" + start = time.time() + snapshot = state["snapshot"] + + tool = DetectIssueTool() + result = await tool.execute(snapshot) + + trace = { + "stage": "detect_issue", + "duration_ms": int((time.time() - start) * 1000), + "output": result + } + + return { + "issue_type": result["issue_type"], + "risk_level": result["risk_level"], + "stage_traces": state.get("stage_traces", []) + [trace] + } + + +async def build_graph_node(state: GitAnalysisState) -> dict: + """Stage 2: Build repository visualization graph.""" + start = time.time() + + tool = BuildGraphTool() + graph = await tool.execute(state["snapshot"]) + + trace = { + "stage": "build_graph", + "duration_ms": int((time.time() - start) * 1000), + "output": {"nodes": len(graph["nodes"]), "edges": len(graph["edges"])} + } + + return { + "repo_graph": graph, + "stage_traces": state.get("stage_traces", []) + [trace] + } + + +async def extract_conflicts_node(state: GitAnalysisState) -> dict: + """Stage 3: Extract conflict information.""" + start = time.time() + + if state["issue_type"] != "merge_conflict": + return {"conflicts": [], "stage_traces": state.get("stage_traces", [])} + + tool = ExtractConflictsTool() + options = state.get("options", {}) + conflicts = await tool.execute( + state["snapshot"], + max_files=options.get("maxConflictFiles", 10), + max_hunks=options.get("maxHunksPerFile", 5) + ) + + trace = { + "stage": "extract_conflicts", + "duration_ms": int((time.time() - start) * 1000), + "output": {"conflict_count": len(conflicts)} + } + + return { + "conflicts": conflicts, + "stage_traces": state.get("stage_traces", []) + [trace] + } + + +async def collect_signals_node(state: GitAnalysisState) -> dict: + """Stage 4: Collect normalized signals for AI analysis.""" + start = time.time() + snapshot = state["snapshot"] + + signals = { + "primaryIssue": state["issue_type"], + "riskLevel": state["risk_level"], + "conflictCount": len(state.get("conflicts", [])), + "isDetachedHead": snapshot.get("isDetachedHead", False), + "isRebaseInProgress": snapshot.get("rebaseState", {}).get("inProgress", False), + "currentBranch": snapshot.get("branch", {}).get("head", "unknown"), + "hasStagedChanges": len(snapshot.get("stagedFiles", [])) > 0, + "hasUnstagedChanges": len(snapshot.get("modifiedFiles", [])) > 0, + "hasUntrackedFiles": len(snapshot.get("untrackedFiles", [])) > 0, + "recentActions": [ + entry.get("action", "") for entry in snapshot.get("recentReflog", [])[:5] + ], + } + + trace = { + "stage": "collect_signals", + "duration_ms": int((time.time() - start) * 1000), + "output": signals + } + + return { + "signals": signals, + "stage_traces": state.get("stage_traces", []) + [trace] + } + + +async def generate_analysis_node(state: GitAnalysisState) -> dict: + """Stage 5: Use LLM to generate analysis, explanations, and plan.""" + start = time.time() + + snapshot = state["snapshot"] + signals = state["signals"] + conflicts = state.get("conflicts", []) + + # Build detailed context for LLM + context = f"""You are GitGuard, an expert Git recovery assistant. Analyze this repository state and provide specific, actionable guidance. + +## Current Situation +- **Issue Type**: {signals["primaryIssue"].replace("_", " ").title()} +- **Risk Level**: {signals["riskLevel"].upper()} +- **Branch**: {signals["currentBranch"]} +- **Conflicts**: {signals["conflictCount"]} file(s) +- **Detached HEAD**: {"Yes" if signals["isDetachedHead"] else "No"} +- **Rebase in Progress**: {"Yes" if signals["isRebaseInProgress"] else "No"} + +## Repository State +- Staged files: {len(snapshot.get("stagedFiles", []))} +- Modified files: {len(snapshot.get("modifiedFiles", []))} +- Untracked files: {len(snapshot.get("untrackedFiles", []))} + +## Recent Git Actions +{chr(10).join(f"- {action}" for action in signals["recentActions"]) if signals["recentActions"] else "No recent actions recorded"} +""" + + if conflicts: + context += "\n## Conflict Details\n" + for cf in conflicts[:5]: + context += f"\n### {cf['path']} ({cf['hunkCount']} hunks, {cf['severity']} severity)\n" + for hunk in cf["hunks"][:2]: + context += f""" +**Hunk {hunk['index'] + 1}** (lines {hunk.get('startLine', '?')}-{hunk.get('endLine', '?')}): +- OURS ({hunk['linesAdded']} lines): +``` +{hunk['oursText'][:300]}{'...' if len(hunk['oursText']) > 300 else ''} +``` +- THEIRS ({hunk['linesRemoved']} lines): +``` +{hunk['theirsText'][:300]}{'...' if len(hunk['theirsText']) > 300 else ''} +``` +""" + + prompt = f"""{context} + +## Your Task +Provide a comprehensive analysis in JSON format with these fields: + +1. **summary**: A clear 2-3 sentence explanation of what happened and why (be specific to THIS situation, not generic) + +2. **conflictExplanations**: For each conflict file, provide: + - "path": file path + - "whatHappened": Specific explanation of what each side changed + - "whyConflict": Why these changes conflict + - "recommendation": Specific resolution strategy (keep ours, keep theirs, or how to combine) + - "priority": "high", "medium", or "low" + +3. **planSteps**: Array of specific recovery steps, each with: + - "title": Clear action title + - "description": Detailed explanation of what this step does and why + - "commands": Exact git commands to run (with actual file names from the conflicts) + - "expectedOutput": What user should see after running + - "verify": Commands to verify success + - "undo": Commands to undo if something goes wrong + - "dangerLevel": "safe", "caution", or "dangerous" + - "estimatedTime": "quick" (< 1 min), "moderate" (1-5 min), or "careful" (> 5 min) + +4. **quickActions**: Array of 2-3 one-click actions for common resolutions: + - "label": Button label + - "command": Single git command + - "description": What it does + +Be SPECIFIC to this user's actual situation. Reference actual file names and branch names. Focus on SAFE, REVERSIBLE solutions. + +Respond with ONLY valid JSON, no markdown code blocks.""" + + try: + response = await llm.chat(prompt) + content = response if isinstance(response, str) else response.content + + # Parse JSON response + try: + result = json.loads(content) + except json.JSONDecodeError: + # Try to extract from markdown + if "```json" in content: + json_str = content.split("```json")[1].split("```")[0] + result = json.loads(json_str) + elif "```" in content: + json_str = content.split("```")[1].split("```")[0] + result = json.loads(json_str) + else: + raise + + trace = { + "stage": "generate_analysis", + "duration_ms": int((time.time() - start) * 1000), + "output": {"has_summary": "summary" in result, "plan_steps": len(result.get("planSteps", []))} + } + + return { + "summary": result.get("summary", "Analysis complete."), + "conflict_explanations": { + exp["path"]: exp for exp in result.get("conflictExplanations", []) + }, + "plan_steps": result.get("planSteps", []), + "stage_traces": state.get("stage_traces", []) + [trace] + } + + except Exception as e: + trace = { + "stage": "generate_analysis", + "duration_ms": int((time.time() - start) * 1000), + "error": str(e) + } + + # Fallback plan + return { + "summary": f"Detected {signals['primaryIssue'].replace('_', ' ')} in your repository.", + "conflict_explanations": {}, + "plan_steps": generate_fallback_plan(signals["primaryIssue"], snapshot, conflicts), + "stage_traces": state.get("stage_traces", []) + [trace] + } + + +def generate_fallback_plan(issue_type: str, snapshot: dict, conflicts: list) -> list: + """Generate fallback plan without AI.""" + branch = snapshot.get("branch", {}).get("head", "main") + + if issue_type == "merge_conflict": + conflict_files = [c["path"] for c in conflicts] + return [ + { + "title": "Review Conflict Files", + "description": f"You have conflicts in {len(conflicts)} file(s): {', '.join(conflict_files[:3])}. Review each to understand what changed.", + "commands": ["git status", "git diff --name-only --diff-filter=U"], + "expectedOutput": "List of files with UU (unmerged) status", + "verify": ["git status"], + "undo": [], + "dangerLevel": "safe", + "estimatedTime": "quick" + }, + { + "title": "Resolve Conflicts", + "description": "Open each conflicted file and resolve the conflict markers (<<<<<<, ======, >>>>>>).", + "commands": [f"# Edit {f}" for f in conflict_files[:3]], + "expectedOutput": "Files no longer contain conflict markers", + "verify": ["git diff"], + "undo": ["git checkout --conflict=merge "], + "dangerLevel": "safe", + "estimatedTime": "careful" + }, + { + "title": "Stage Resolved Files", + "description": "Mark conflicts as resolved by staging the files.", + "commands": [f"git add {f}" for f in conflict_files[:3]], + "expectedOutput": "Files moved from 'Unmerged' to 'Staged'", + "verify": ["git status"], + "undo": ["git reset HEAD "], + "dangerLevel": "safe", + "estimatedTime": "quick" + }, + { + "title": "Complete Merge", + "description": "Commit the merge resolution.", + "commands": ["git commit -m 'Resolve merge conflicts'"], + "expectedOutput": "Merge commit created", + "verify": ["git log -1"], + "undo": ["git reset --soft HEAD~1"], + "dangerLevel": "caution", + "estimatedTime": "quick" + } + ] + elif issue_type == "detached_head": + return [ + { + "title": "Check Current Position", + "description": "See where HEAD is pointing and what branches exist.", + "commands": ["git log --oneline -5", "git branch -a"], + "expectedOutput": "Current commit history and available branches", + "verify": ["git status"], + "undo": [], + "dangerLevel": "safe", + "estimatedTime": "quick" + }, + { + "title": "Save Your Work", + "description": "Create a branch to preserve current commits before moving.", + "commands": ["git branch temp-save-work"], + "expectedOutput": "New branch 'temp-save-work' created at current commit", + "verify": ["git branch"], + "undo": ["git branch -d temp-save-work"], + "dangerLevel": "safe", + "estimatedTime": "quick" + }, + { + "title": f"Return to {branch}", + "description": f"Switch back to your main working branch '{branch}'.", + "commands": [f"git checkout {branch}"], + "expectedOutput": f"Switched to branch '{branch}'", + "verify": ["git status"], + "undo": ["git checkout temp-save-work"], + "dangerLevel": "safe", + "estimatedTime": "quick" + } + ] + elif issue_type == "rebase_in_progress": + return [ + { + "title": "Check Rebase Status", + "description": "Understand where you are in the rebase process.", + "commands": ["git status", "git rebase --show-current-patch"], + "expectedOutput": "Current rebase step and conflict details", + "verify": [], + "undo": [], + "dangerLevel": "safe", + "estimatedTime": "quick" + }, + { + "title": "Option A: Continue Rebase", + "description": "If you've resolved conflicts, continue the rebase.", + "commands": ["git add .", "git rebase --continue"], + "expectedOutput": "Rebase continues to next commit or completes", + "verify": ["git status"], + "undo": ["git rebase --abort"], + "dangerLevel": "caution", + "estimatedTime": "moderate" + }, + { + "title": "Option B: Abort Rebase", + "description": "Cancel the rebase and return to the original state.", + "commands": ["git rebase --abort"], + "expectedOutput": "Returns to state before rebase started", + "verify": ["git status", "git log -3"], + "undo": [], + "dangerLevel": "safe", + "estimatedTime": "quick" + } + ] + else: + return [ + { + "title": "Check Status", + "description": "Review the current repository state.", + "commands": ["git status", "git log --oneline -5"], + "expectedOutput": "Current branch and recent commits", + "verify": [], + "undo": [], + "dangerLevel": "safe", + "estimatedTime": "quick" + } + ] + + +# ========================================== +# Build SpoonOS Graph +# ========================================== + +def create_analysis_graph() -> StateGraph: + """Create the SpoonOS graph for git analysis pipeline.""" + graph = StateGraph(GitAnalysisState) + + # Add nodes + graph.add_node("detect_issue", detect_issue_node) + graph.add_node("build_graph", build_graph_node) + graph.add_node("extract_conflicts", extract_conflicts_node) + graph.add_node("collect_signals", collect_signals_node) + graph.add_node("generate_analysis", generate_analysis_node) + + # Set entry point + graph.set_entry_point("detect_issue") + + # Define edges (sequential pipeline) + graph.add_edge("detect_issue", "build_graph") + graph.add_edge("build_graph", "extract_conflicts") + graph.add_edge("extract_conflicts", "collect_signals") + graph.add_edge("collect_signals", "generate_analysis") + graph.add_edge("generate_analysis", END) + + return graph.compile() + + +# Create the compiled graph +analysis_pipeline = create_analysis_graph() + + +# ========================================== +# Request/Response Models +# ========================================== + +class AnalyzeOptions(BaseModel): + includeGraph: bool = True + maxConflictFiles: int = 10 + maxHunksPerFile: int = 5 + + +class AnalyzeRequest(BaseModel): + snapshot: dict + options: Optional[AnalyzeOptions] = None + + +class AnalyzeResponse(BaseModel): + success: bool + analysis: Optional[dict] = None + error: Optional[str] = None + durationMs: Optional[int] = None + pipelineTraces: Optional[list] = None + + +# ========================================== +# API Endpoints +# ========================================== + +@app.get("/health") +async def health_check(): + """Health check endpoint.""" + return { + "status": "healthy", + "service": "gitguard-agent", + "version": "2.0.0", + "framework": "SpoonOS" + } + + +@app.post("/analyze", response_model=AnalyzeResponse) +async def analyze_snapshot(request: AnalyzeRequest): + """Analyze a git snapshot using SpoonOS graph pipeline.""" + start_time = time.time() + + try: + # Initialize state + initial_state: GitAnalysisState = { + "snapshot": request.snapshot, + "options": request.options.model_dump() if request.options else {}, + "issue_type": "", + "risk_level": "", + "repo_graph": {}, + "conflicts": [], + "signals": {}, + "summary": "", + "conflict_explanations": {}, + "plan_steps": [], + "stage_traces": [], + "error": "" + } + + # Run the SpoonOS pipeline + result = await analysis_pipeline.invoke(initial_state) + + duration_ms = int((time.time() - start_time) * 1000) + + # Enhance conflicts with explanations + conflicts_with_explanations = [] + for conflict in result.get("conflicts", []): + explanation = result.get("conflict_explanations", {}).get(conflict["path"], {}) + conflicts_with_explanations.append({ + **conflict, + "whatHappened": explanation.get("whatHappened"), + "whyConflict": explanation.get("whyConflict"), + "recommendation": explanation.get("recommendation"), + "priority": explanation.get("priority", "medium") + }) + + return AnalyzeResponse( + success=True, + analysis={ + "issueType": result["issue_type"], + "riskLevel": result["risk_level"], + "summary": result["summary"], + "repoGraph": result["repo_graph"], + "conflicts": conflicts_with_explanations, + "planSteps": result["plan_steps"], + "signals": result["signals"] + }, + durationMs=duration_ms, + pipelineTraces=result.get("stage_traces", []) + ) + + except Exception as e: + duration_ms = int((time.time() - start_time) * 1000) + return AnalyzeResponse( + success=False, + error=str(e), + durationMs=duration_ms + ) + + +@app.post("/explain/conflict") +async def explain_conflict(file_path: str, hunk_index: int, ours: str, theirs: str): + """Get detailed AI explanation for a specific conflict hunk.""" + prompt = f"""Analyze this specific git merge conflict and provide guidance: + +**File**: {file_path} +**Hunk**: #{hunk_index + 1} + +**OURS (your current branch)**: +``` +{ours} +``` + +**THEIRS (incoming changes)**: +``` +{theirs} +``` + +Provide a JSON response with: +1. "oursIntent": What your code is trying to do +2. "theirsIntent": What the incoming code is trying to do +3. "conflictReason": Why these changes conflict +4. "recommendation": "keep_ours", "keep_theirs", "combine", or "needs_review" +5. "combinedCode": If recommend combine, provide the merged code +6. "explanation": Human-readable explanation of your recommendation + +Be specific and reference the actual code.""" + + try: + response = await llm.chat(prompt) + content = response if isinstance(response, str) else response.content + + try: + result = json.loads(content) + except: + if "```json" in content: + result = json.loads(content.split("```json")[1].split("```")[0]) + else: + result = {"explanation": content} + + return {"success": True, "explanation": result} + except Exception as e: + return {"success": False, "error": str(e)} + + +if __name__ == "__main__": + import uvicorn + port = int(os.getenv("PORT", "8000")) + host = os.getenv("HOST", "0.0.0.0") + print(f"Starting GitGuard Agent with SpoonOS on {host}:{port}") + uvicorn.run(app, host=host, port=port) diff --git a/apps/agent/requirements.txt b/apps/agent/requirements.txt new file mode 100644 index 0000000..596cfb1 --- /dev/null +++ b/apps/agent/requirements.txt @@ -0,0 +1,7 @@ +fastapi>=0.104.0 +uvicorn[standard]>=0.24.0 +pydantic>=2.5.0 +httpx>=0.25.0 +python-dotenv>=1.0.0 +anthropic>=0.39.0 +spoon-ai>=0.1.0 diff --git a/apps/cli/src/commands/send.ts b/apps/cli/src/commands/send.ts new file mode 100644 index 0000000..d25657c --- /dev/null +++ b/apps/cli/src/commands/send.ts @@ -0,0 +1,166 @@ +import { SnapshotV1Schema, type SnapshotV1 } from '@gitguard/schema'; +import { collectGitInfo } from '../collectors/git-info.js'; +import { parseStatus } from '../parsers/status-parser.js'; +import { parseBranches } from '../parsers/branch-parser.js'; +import { parseLog } from '../parsers/log-parser.js'; +import { parseReflog } from '../parsers/reflog-parser.js'; +import { parseDiffStat } from '../parsers/diffstat-parser.js'; +import { detectRebaseState } from '../collectors/rebase-detector.js'; +import { extractConflicts } from '../collectors/conflict-extractor.js'; + +const DEFAULT_API_URL = 'http://localhost:3000'; + +interface SendOptions { + apiUrl?: string; + open?: boolean; +} + +interface IngestResponse { + sessionId: string; + url: string; + analysis: { + issueType: string; + summary: string; + }; +} + +export async function sendCommand(options: SendOptions): Promise { + const apiUrl = options.apiUrl || process.env.GITGUARD_API_URL || DEFAULT_API_URL; + + console.error('Collecting repository state...'); + + try { + // Collect git information + const gitInfo = await collectGitInfo(); + + // Parse status + const statusInfo = parseStatus(gitInfo.status); + + // Parse branches + const branchInfo = parseBranches(gitInfo.branches, statusInfo.branch); + + // Parse logs + const logEntries = parseLog(gitInfo.log); + const reflogEntries = parseReflog(gitInfo.reflog); + + // Parse diff stats + const diffStats = parseDiffStat(gitInfo.diffStat); + + // Detect rebase state + const rebaseState = await detectRebaseState(gitInfo.gitDir); + + // Extract conflict details + const unmergedFiles = await extractConflicts( + gitInfo.repoRoot, + statusInfo.unmergedPaths.slice(0, 10) // Extract up to 10 conflict files + ); + + // Build snapshot + const snapshot: SnapshotV1 = { + version: 1, + timestamp: new Date().toISOString(), + platform: process.platform as 'win32' | 'darwin' | 'linux', + repoRoot: gitInfo.repoRoot, + gitDir: gitInfo.gitDir, + + branch: branchInfo, + isDetachedHead: statusInfo.isDetachedHead, + + rebaseState, + + unmergedFiles, + stagedFiles: statusInfo.stagedFiles, + modifiedFiles: statusInfo.modifiedFiles, + untrackedFiles: statusInfo.untrackedFiles, + + recentLog: logEntries, + recentReflog: reflogEntries, + + commitGraph: gitInfo.commitGraph || undefined, + diffStats: diffStats.length > 0 ? diffStats : undefined, + mergeHead: gitInfo.mergeHead || undefined, + mergeMessage: gitInfo.mergeMessage || undefined, + + rawStatus: gitInfo.status, + rawBranches: gitInfo.branches, + }; + + // Validate with Zod + const validated = SnapshotV1Schema.parse(snapshot); + + console.error('Uploading snapshot to GitGuard...'); + + // Send to API + const response = await fetch(`${apiUrl}/api/snapshots/ingest`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ snapshot: validated }), + }); + + if (!response.ok) { + let errorMessage = `Server returned ${response.status}`; + try { + const errorData = await response.json() as { error?: string }; + if (errorData.error) { + errorMessage = errorData.error; + } + } catch { + // Ignore JSON parse errors + } + throw new Error(errorMessage); + } + + const result = await response.json() as IngestResponse; + + // Display results + console.log(''); + console.log('='.repeat(60)); + console.log(''); + console.log(` Issue Type: ${result.analysis.issueType.replace('_', ' ').toUpperCase()}`); + console.log(` Summary: ${result.analysis.summary}`); + console.log(''); + console.log(` Incident Room: ${result.url}`); + console.log(''); + console.log('='.repeat(60)); + console.log(''); + + // Try to open in browser if requested + if (options.open) { + try { + const open = await getOpenCommand(); + if (open) { + const { exec } = await import('node:child_process'); + exec(`${open} "${result.url}"`); + console.error('Opening in browser...'); + } + } catch { + // Silently fail if we can't open the browser + } + } + } catch (error) { + if (error instanceof Error) { + console.error(`Error: ${error.message}`); + if (error.message.includes('not a git repository')) { + console.error('Please run this command from within a git repository.'); + } else if (error.message.includes('fetch')) { + console.error(`Could not connect to ${apiUrl}. Is the server running?`); + } + } + process.exit(1); + } +} + +async function getOpenCommand(): Promise { + switch (process.platform) { + case 'darwin': + return 'open'; + case 'win32': + return 'start'; + case 'linux': + return 'xdg-open'; + default: + return null; + } +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 9b5b5e0..71f87d3 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node import { program } from 'commander'; import { snapshotCommand } from './commands/snapshot.js'; +import { sendCommand } from './commands/send.js'; program .name('gitguard') @@ -14,4 +15,11 @@ program .option('--pretty', 'Pretty-print JSON output') .action(snapshotCommand); +program + .command('send') + .description('Capture and send repository state to GitGuard for analysis') + .option('-u, --api-url ', 'GitGuard API URL (default: http://localhost:3000)') + .option('-o, --open', 'Open the incident room in browser after upload') + .action(sendCommand); + program.parse(); diff --git a/apps/scoop-hackathon/.env.example b/apps/scoop-hackathon/.env.example deleted file mode 100644 index 68e7bc9..0000000 --- a/apps/scoop-hackathon/.env.example +++ /dev/null @@ -1,45 +0,0 @@ -# Application Configuration -APP_NAME=scoop-hackathon -SERVER_PORT=8080 - -# Database Configuration -# MySQL Database Configuration -DB_TYPE=mysql -DB_URL=jdbc:mysql://localhost:3306/scoop_hackathon?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true -DB_DRIVER=com.mysql.cj.jdbc.Driver -DB_USERNAME=root -DB_PASSWORD=your_mysql_password - -# PostgreSQL Configuration (alternative, uncomment to use) -# DB_TYPE=postgresql -# DB_URL=jdbc:postgresql://localhost:5432/scoop_hackathon -# DB_DRIVER=org.postgresql.Driver -# DB_USERNAME=your_postgres_username -# DB_PASSWORD=your_postgres_password - -# JPA/Hibernate Configuration -JPA_DDL_AUTO=validate -JPA_SHOW_SQL=true -JPA_FORMAT_SQL=true -JPA_DIALECT=org.hibernate.dialect.MySQLDialect - -# Flyway Configuration -FLYWAY_ENABLED=true -FLYWAY_BASELINE_ON_MIGRATE=true -FLYWAY_BASELINE_VERSION=0 -FLYWAY_VALIDATE_ON_MIGRATE=false - -# Security Configuration -JWT_SECRET=your-secret-key-change-this-in-production -JWT_EXPIRATION=86400000 - -# API Keys (if needed) -# OPENAI_API_KEY=your_openai_api_key -# ANTHROPIC_API_KEY=your_anthropic_api_key -# DEEPSEEK_API_KEY=your_deepseek_api_key - -# Logging Configuration -LOG_LEVEL_ROOT=INFO -LOG_LEVEL_WEB=INFO -LOG_LEVEL_HIBERNATE=DEBUG -LOG_LEVEL_APP=DEBUG diff --git a/apps/scoop-hackathon/.gitignore b/apps/scoop-hackathon/.gitignore deleted file mode 100644 index c6deb2b..0000000 --- a/apps/scoop-hackathon/.gitignore +++ /dev/null @@ -1,96 +0,0 @@ -# Maven -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -.mvn/wrapper/maven-wrapper.jar - -# IDE -.idea/ -*.iml -*.iws -*.ipr -.vscode/ -.settings/ -.classpath -.project -.factorypath - -# Eclipse -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.loadpath -.recommenders - -# NetBeans -nbproject/private/ -build/ -nbbuild/ -dist/ -nbdist/ -.nb-gradle/ - -# VS Code -.vscode/ - -# Mac -.DS_Store - -# Java -*.class -*.log -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar -hs_err_pid* - -# Spring Boot -application-local.properties -application-dev.properties -application-prod.properties - -# Database -*.db -*.sqlite -*.h2.db - -# Logs -logs/ -*.log - -# Python virtual environment (if any) -spoon-env/ -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Environment variables -.env -.env.local -.env.*.local -!.env.example - -# OS -Thumbs.db - diff --git a/apps/scoop-hackathon/CLEAN_FLYWAY.md b/apps/scoop-hackathon/CLEAN_FLYWAY.md deleted file mode 100644 index 1a94c57..0000000 --- a/apps/scoop-hackathon/CLEAN_FLYWAY.md +++ /dev/null @@ -1,42 +0,0 @@ -# Fixing Flyway Failed Migrations - -If you encounter the error "Schema contains a failed migration", you have a few options: - -## Option 1: Use the repair script (Recommended) - -Run the provided script: -```bash -./fix_flyway.sh -``` - -## Option 2: Manual MySQL repair - -Connect to MySQL and delete the failed migration record: - -```sql -USE gitguard_agent; -DELETE FROM flyway_schema_history WHERE success = 0; -``` - -Or repair all: -```sql -USE gitguard_agent; -DELETE FROM flyway_schema_history; -``` - -## Option 3: Drop and recreate database - -```sql -DROP DATABASE IF EXISTS gitguard_agent; -CREATE DATABASE gitguard_agent; -``` - -Then restart the application - it will create everything fresh. - -## Option 4: Use Flyway repair via Maven - -```bash -mvn flyway:repair -``` - -Note: Make sure Flyway Maven plugin is configured in pom.xml diff --git a/apps/scoop-hackathon/MYSQL_QUERIES.md b/apps/scoop-hackathon/MYSQL_QUERIES.md deleted file mode 100644 index 70339db..0000000 --- a/apps/scoop-hackathon/MYSQL_QUERIES.md +++ /dev/null @@ -1,390 +0,0 @@ -# MySQL Database Inspection Queries - -Useful SQL queries to check and inspect your MySQL database tables. - -## Database Connection -```bash -mysql -u root -p gitguard_agent -``` - -Or if no password: -```bash -mysql -u root gitguard_agent -``` - ---- - -## 1. List All Tables -```sql --- Show all tables in the database -SHOW TABLES; - --- Or using information schema -SELECT TABLE_NAME -FROM information_schema.TABLES -WHERE TABLE_SCHEMA = 'gitguard_agent'; -``` - ---- - -## 2. Check Table Structure (Columns) -```sql --- Describe table structure -DESCRIBE user; -DESCRIBE account; -DESCRIBE session; -DESCRIBE git_session; -DESCRIBE event; -DESCRIBE snapshot; -DESCRIBE trace; -DESCRIBE plan; -DESCRIBE verification_token; -DESCRIBE authentication_history; - --- Or using SHOW COLUMNS -SHOW COLUMNS FROM user; -SHOW COLUMNS FROM authentication_history; - --- Or detailed information -SELECT - COLUMN_NAME, - DATA_TYPE, - IS_NULLABLE, - COLUMN_DEFAULT, - COLUMN_KEY, - EXTRA -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = 'gitguard_agent' - AND TABLE_NAME = 'user' -ORDER BY ORDINAL_POSITION; -``` - ---- - -## 3. View Table Data -```sql --- View all users -SELECT * FROM user; - --- View all accounts -SELECT * FROM account; - --- View all sessions -SELECT * FROM session; - --- View all git sessions -SELECT * FROM git_session; - --- View all events -SELECT * FROM event; - --- View all snapshots -SELECT * FROM snapshot; - --- View all traces -SELECT * FROM trace; - --- View all plans -SELECT * FROM plan; - --- View all verification tokens -SELECT * FROM verification_token; - --- View all authentication history -SELECT * FROM authentication_history; -``` - ---- - -## 4. Count Records in Each Table -```sql --- Count records in all tables -SELECT - 'user' AS table_name, COUNT(*) AS record_count FROM user -UNION ALL -SELECT 'account', COUNT(*) FROM account -UNION ALL -SELECT 'session', COUNT(*) FROM session -UNION ALL -SELECT 'git_session', COUNT(*) FROM git_session -UNION ALL -SELECT 'event', COUNT(*) FROM event -UNION ALL -SELECT 'snapshot', COUNT(*) FROM snapshot -UNION ALL -SELECT 'trace', COUNT(*) FROM trace -UNION ALL -SELECT 'plan', COUNT(*) FROM plan -UNION ALL -SELECT 'verification_token', COUNT(*) FROM verification_token -UNION ALL -SELECT 'authentication_history', COUNT(*) FROM authentication_history; -``` - ---- - -## 5. Check Table Indexes -```sql --- Show indexes for a specific table -SHOW INDEXES FROM user; -SHOW INDEXES FROM authentication_history; -SHOW INDEXES FROM git_session; - --- Or using information schema -SELECT - TABLE_NAME, - INDEX_NAME, - COLUMN_NAME, - SEQ_IN_INDEX, - NON_UNIQUE -FROM information_schema.STATISTICS -WHERE TABLE_SCHEMA = 'gitguard_agent' - AND TABLE_NAME = 'user' -ORDER BY INDEX_NAME, SEQ_IN_INDEX; -``` - ---- - -## 6. Check Foreign Key Constraints -```sql --- Show foreign keys for a specific table -SELECT - CONSTRAINT_NAME, - TABLE_NAME, - COLUMN_NAME, - REFERENCED_TABLE_NAME, - REFERENCED_COLUMN_NAME -FROM information_schema.KEY_COLUMN_USAGE -WHERE TABLE_SCHEMA = 'gitguard_agent' - AND REFERENCED_TABLE_NAME IS NOT NULL -ORDER BY TABLE_NAME, CONSTRAINT_NAME; - --- Check foreign keys for a specific table -SELECT - CONSTRAINT_NAME, - TABLE_NAME, - COLUMN_NAME, - REFERENCED_TABLE_NAME, - REFERENCED_COLUMN_NAME -FROM information_schema.KEY_COLUMN_USAGE -WHERE TABLE_SCHEMA = 'gitguard_agent' - AND TABLE_NAME = 'account'; -``` - ---- - -## 7. Check Table Sizes -```sql --- Get table sizes (data + index) -SELECT - TABLE_NAME AS `Table`, - ROUND(((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024), 2) AS `Size (MB)`, - ROUND((DATA_LENGTH / 1024 / 1024), 2) AS `Data (MB)`, - ROUND((INDEX_LENGTH / 1024 / 1024), 2) AS `Index (MB)`, - TABLE_ROWS AS `Rows` -FROM information_schema.TABLES -WHERE TABLE_SCHEMA = 'gitguard_agent' -ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC; -``` - ---- - -## 8. Useful Data Queries - -### Users with their accounts -```sql -SELECT - u.id, - u.name, - u.email, - u.email_verified, - u.created_at, - COUNT(a.id) AS account_count -FROM user u -LEFT JOIN account a ON u.id = a.user_id -GROUP BY u.id, u.name, u.email, u.email_verified, u.created_at; -``` - -### Active sessions -```sql -SELECT - s.id, - s.session_token, - s.expires, - u.email, - u.name -FROM session s -JOIN user u ON s.user_id = u.id -WHERE s.expires > NOW() -ORDER BY s.expires DESC; -``` - -### Git sessions with user info -```sql -SELECT - gs.id, - gs.title, - gs.os, - gs.created_at, - u.email, - u.name -FROM git_session gs -LEFT JOIN user u ON gs.user_id = u.id -ORDER BY gs.created_at DESC; -``` - -### Recent authentication history -```sql -SELECT - ah.id, - ah.action, - ah.success, - ah.ip_address, - ah.created_at, - u.email -FROM authentication_history ah -LEFT JOIN user u ON ah.user_id = u.id -ORDER BY ah.created_at DESC -LIMIT 50; -``` - -### Failed authentication attempts -```sql -SELECT - ah.id, - ah.action, - ah.failure_reason, - ah.ip_address, - ah.user_agent, - ah.created_at, - u.email -FROM authentication_history ah -LEFT JOIN user u ON ah.user_id = u.id -WHERE ah.success = FALSE -ORDER BY ah.created_at DESC; -``` - -### Events by type -```sql -SELECT - type, - COUNT(*) AS count -FROM event -GROUP BY type -ORDER BY count DESC; -``` - -### Traces with success/failure stats -```sql -SELECT - stage, - COUNT(*) AS total, - SUM(CASE WHEN success = TRUE THEN 1 ELSE 0 END) AS successful, - SUM(CASE WHEN success = FALSE THEN 1 ELSE 0 END) AS failed, - AVG(duration_ms) AS avg_duration_ms -FROM trace -GROUP BY stage -ORDER BY total DESC; -``` - ---- - -## 9. Check Table Engine and Charset -```sql -SELECT - TABLE_NAME, - ENGINE, - TABLE_COLLATION, - TABLE_ROWS, - CREATE_TIME, - UPDATE_TIME -FROM information_schema.TABLES -WHERE TABLE_SCHEMA = 'gitguard_agent' -ORDER BY TABLE_NAME; -``` - ---- - -## 10. Check for Orphaned Records -```sql --- Check for orphaned accounts (user_id doesn't exist) -SELECT a.id, a.user_id, a.provider -FROM account a -LEFT JOIN user u ON a.user_id = u.id -WHERE u.id IS NULL; - --- Check for orphaned sessions -SELECT s.id, s.user_id, s.session_token -FROM session s -LEFT JOIN user u ON s.user_id = u.id -WHERE u.id IS NULL; - --- Check for orphaned git_sessions -SELECT gs.id, gs.user_id, gs.title -FROM git_session gs -LEFT JOIN user u ON gs.user_id = u.id -WHERE gs.user_id IS NOT NULL AND u.id IS NULL; -``` - ---- - -## 11. Check Expired Tokens/Sessions -```sql --- Expired verification tokens -SELECT * FROM verification_token -WHERE expires < NOW(); - --- Expired sessions -SELECT * FROM session -WHERE expires < NOW(); - --- Active (non-expired) sessions -SELECT * FROM session -WHERE expires > NOW(); -``` - ---- - -## 12. Database Information -```sql --- Database size -SELECT - TABLE_SCHEMA AS 'Database', - ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS 'Size (MB)' -FROM information_schema.TABLES -WHERE TABLE_SCHEMA = 'gitguard_agent'; - --- MySQL version -SELECT VERSION(); - --- Current database -SELECT DATABASE(); - --- Current user -SELECT USER(); -``` - ---- - -## Quick Reference Commands - -```sql --- Connect to database -USE gitguard_agent; - --- Show all tables -SHOW TABLES; - --- Describe a table -DESC user; - --- Select with limit -SELECT * FROM user LIMIT 10; - --- Count records -SELECT COUNT(*) FROM user; - --- Check if table exists -SHOW TABLES LIKE 'user'; -``` - diff --git a/apps/scoop-hackathon/POSTMAN_SIGNUP_GUIDE.md b/apps/scoop-hackathon/POSTMAN_SIGNUP_GUIDE.md deleted file mode 100644 index 702f899..0000000 --- a/apps/scoop-hackathon/POSTMAN_SIGNUP_GUIDE.md +++ /dev/null @@ -1,256 +0,0 @@ -# Postman Guide: Client Signup to MySQL Database - -This guide shows you how to sign up a client using Postman to create a user in your MySQL database. - -## Prerequisites - -1. **Application is running** - Make sure your Spring Boot application is running -2. **MySQL database is running** - Ensure MySQL is running and accessible -3. **Postman installed** - Download from [postman.com](https://www.postman.com/downloads/) - ---- - -## Step-by-Step Instructions - -### 1. Start Your Application - -First, make sure your Spring Boot application is running: -```bash -# From the project root directory -mvn spring-boot:run -``` - -The application should start on port **8080** by default (check your `application.properties`). - ---- - -### 2. Open Postman - -1. Open Postman application -2. Create a new request or use an existing one - ---- - -### 3. Configure the Request - -#### Request Method and URL -- **Method**: `POST` -- **URL**: `http://localhost:8080/api/auth/signup/client` - -#### Headers -Click on the **Headers** tab and add: -- **Key**: `Content-Type` -- **Value**: `application/json` - -#### Body -1. Click on the **Body** tab -2. Select **raw** -3. Select **JSON** from the dropdown (next to "raw") -4. Enter the following JSON: - -```json -{ - "name": "John Doe", - "email": "john.doe@example.com", - "password": "SecurePass123" -} -``` - -**Field Requirements:** -- `name`: Required, 2-100 characters -- `email`: Required, must be a valid email format -- `password`: Required, minimum 8 characters - ---- - -### 4. Send the Request - -Click the **Send** button. You should receive one of these responses: - -#### ✅ Success Response (201 Created) -```json -{ - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "userId": "clx1234567890abcdef", - "email": "john.doe@example.com", - "name": "John Doe", - "expiresAt": "2024-01-02T12:00:00" -} -``` - -#### ❌ Error Responses - -**400 Bad Request - Email Already Exists:** -```json -{ - "timestamp": "2024-01-01T12:00:00", - "status": 400, - "error": "Bad Request", - "message": "Client with email john.doe@example.com already exists", - "path": "/api/auth/signup/client" -} -``` - -**400 Bad Request - Validation Error:** -```json -{ - "timestamp": "2024-01-01T12:00:00", - "status": 400, - "error": "Bad Request", - "message": "Validation failed", - "errors": [ - { - "field": "password", - "message": "Password must be at least 8 characters long" - } - ], - "path": "/api/auth/signup/client" -} -``` - ---- - -## Example Postman Collection - -Here's a complete example you can import into Postman: - -### Request Details -``` -POST http://localhost:8080/api/auth/signup/client -Content-Type: application/json - -{ - "name": "Jane Smith", - "email": "jane.smith@example.com", - "password": "MySecurePassword123" -} -``` - ---- - -## Alternative: Using cURL - -If you prefer using command line, here's the equivalent cURL command: - -```bash -curl -X POST http://localhost:8080/api/auth/signup/client \ - -H "Content-Type: application/json" \ - -d '{ - "name": "John Doe", - "email": "john.doe@example.com", - "password": "SecurePass123" - }' -``` - ---- - -## Verify in MySQL Database - -After successful signup, you can verify the user was created in MySQL: - -```sql --- Connect to MySQL -mysql -u root -p gitguard_agent - --- Check the user table -SELECT * FROM user WHERE email = 'john.doe@example.com'; - --- Check authentication history -SELECT * FROM authentication_history -WHERE user_id = (SELECT id FROM user WHERE email = 'john.doe@example.com') -ORDER BY created_at DESC; -``` - ---- - -## Other Available Endpoints - -### Register (Alternative to signup/client) -``` -POST http://localhost:8080/api/auth/register -Content-Type: application/json - -{ - "name": "John Doe", - "email": "john.doe@example.com", - "password": "SecurePass123" -} -``` - -### Login -``` -POST http://localhost:8080/api/auth/login -Content-Type: application/json - -{ - "email": "john.doe@example.com", - "password": "SecurePass123" -} -``` - -### Validate Token -``` -POST http://localhost:8080/api/auth/validate -Authorization: Bearer -``` - ---- - -## Troubleshooting - -### 403 Forbidden Error -- **Problem**: Getting `403 Forbidden` when trying to sign up -- **Solution**: The `/api/auth/signup/client` endpoint must be added to the `permitAll()` list in `SecurityConfig.java`. This has been fixed in the codebase. If you still see this error: - 1. Restart your Spring Boot application - 2. Make sure the SecurityConfig includes `/api/auth/signup/client` in the permitAll list - -### Connection Refused -- **Problem**: `Connection refused` error -- **Solution**: Make sure your Spring Boot application is running on port 8080 - -### 401 Unauthorized -- **Problem**: Getting 401 errors on protected endpoints -- **Solution**: Make sure you include the JWT token in the Authorization header: - ``` - Authorization: Bearer - ``` - -### Email Already Exists -- **Problem**: Getting "email already exists" error -- **Solution**: Use a different email address or delete the existing user from the database - -### Database Connection Error -- **Problem**: Application can't connect to MySQL -- **Solution**: - 1. Check MySQL is running: `mysql -u root -p` - 2. Verify database exists: `SHOW DATABASES;` - 3. Check `application.properties` for correct database credentials - ---- - -## Using the JWT Token - -After successful signup, you'll receive a JWT token. Use this token for authenticated requests: - -1. Copy the `token` value from the response -2. In Postman, go to the **Authorization** tab -3. Select **Bearer Token** from the Type dropdown -4. Paste your token in the Token field - -Or manually add it to headers: -- **Key**: `Authorization` -- **Value**: `Bearer ` - ---- - -## Testing with Postman Collection - -You can create a Postman Collection with these requests: - -1. **Client Signup** - POST `/api/auth/signup/client` -2. **Login** - POST `/api/auth/login` -3. **Validate Token** - POST `/api/auth/validate` -4. **Get Auth History** - GET `/api/auth/history` - -Save these as a collection for easy testing! - diff --git a/apps/scoop-hackathon/README.md b/apps/scoop-hackathon/README.md deleted file mode 100644 index 74e5ed0..0000000 --- a/apps/scoop-hackathon/README.md +++ /dev/null @@ -1,203 +0,0 @@ -# Scoop Hackathon - -Spring Boot application for Scoop Hackathon with full CRUD operations. - -## Technology Stack - -- **Java**: 21 -- **Spring Boot**: 3.2.0 -- **Database**: MySQL (default), PostgreSQL (optional) -- **ORM**: JPA/Hibernate -- **Database Migrations**: Flyway -- **Build Tool**: Maven - -## Project Structure - -``` -src/ -├── main/ -│ ├── java/ -│ │ └── com/scoop/hackathon/ -│ │ ├── config/ # Configuration classes -│ │ ├── controller/ # REST controllers -│ │ ├── dto/ # Data Transfer Objects -│ │ ├── entity/ # JPA entities -│ │ ├── exception/ # Exception handling -│ │ ├── payload/ # API response wrappers -│ │ ├── repository/ # JPA repositories -│ │ ├── security/ # Security configuration -│ │ ├── service/ # Business logic -│ │ └── util/ # Utility classes -│ └── resources/ -│ ├── application.properties -│ └── db/migration/ # Flyway migrations -└── test/ - └── java/ # Test classes -``` - -## Getting Started - -### Prerequisites - -- Java 21 or higher -- Maven 3.6 or higher - -### Configuration - -1. **Environment Variables Setup** - - The application uses `.env` files for sensitive configuration. - - - Copy the example file: - ```bash - cp .env.example .env - ``` - - - Edit `.env` and configure your settings: - - Database credentials (if using PostgreSQL or MySQL) - - API keys (if needed) - - JWT secret key - - Other sensitive information - - **Important**: The `.env` file is gitignored and will not be committed to the repository. Always use `.env.example` as a template. - -2. **Database Configuration** - - The `.env` file supports three database types: - - **H2** (default, in-memory, for development) - - **PostgreSQL** (for production) - - **MySQL** (for production) - - To switch databases, update the `DB_TYPE`, `DB_URL`, `DB_USERNAME`, and `DB_PASSWORD` in your `.env` file. - -### Running the Application - -1. Clone the repository: -```bash -git clone -cd scoop-hackathon -``` - -2. Set up environment variables: -```bash -cp .env.example .env -# Edit .env with your configuration -``` - -3. Build the project: -```bash -mvn clean install -``` - -4. Run the application: -```bash -mvn spring-boot:run -``` - -The application will start on `http://localhost:8080` (or the port specified in `.env`) - -## API Endpoints - -### Users -- `POST /api/users` - Create user -- `GET /api/users/{id}` - Get user by ID -- `GET /api/users` - Get all users -- `PUT /api/users/{id}` - Update user -- `DELETE /api/users/{id}` - Delete user - -### Git Sessions -- `POST /api/git-sessions` - Create GitSession -- `GET /api/git-sessions/{id}` - Get GitSession by ID -- `GET /api/git-sessions` - Get all GitSessions -- `PUT /api/git-sessions/{id}` - Update GitSession -- `DELETE /api/git-sessions/{id}` - Delete GitSession - -Similar endpoints are available for: -- Accounts (`/api/accounts`) -- Sessions (`/api/sessions`) -- Snapshots (`/api/snapshots`) -- Traces (`/api/traces`) -- Plans (`/api/plans`) -- Events (`/api/events`) -- Verification Tokens (`/api/verification-tokens`) - -## Database - -### MySQL Configuration - -The application uses MySQL by default. Make sure MySQL is installed and running. - -**Prerequisites:** -- MySQL 5.7.8+ or MySQL 8.0+ (for JSON support) -- Create a database or let the application create it automatically - -**Configuration:** -- Update your `.env` file with your MySQL credentials: - ``` - DB_URL=jdbc:mysql://localhost:3306/scoop_hackathon?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true - DB_USERNAME=root - DB_PASSWORD=your_mysql_password - ``` - -The application will automatically create the database if it doesn't exist (when `createDatabaseIfNotExist=true` is in the URL). - -### Database Migrations - -Flyway migrations are located in `src/main/resources/db/migration/` - -**If you encounter "failed migration" errors:** - -1. **Quick fix** - Run the SQL script: - ```bash - mysql -u root -p gitguard_agent < fix_flyway.sql - ``` - -2. **Or manually** - Connect to MySQL and run: - ```sql - USE gitguard_agent; - DELETE FROM flyway_schema_history WHERE success = 0; - ``` - -3. **Or drop and recreate** the database for a fresh start: - ```sql - DROP DATABASE IF EXISTS gitguard_agent; - CREATE DATABASE gitguard_agent; - ``` - -See `CLEAN_FLYWAY.md` for more detailed instructions. - -## Environment Variables - -All sensitive configuration is stored in `.env` file. The following variables are available: - -### Application -- `APP_NAME` - Application name -- `SERVER_PORT` - Server port (default: 8080) - -### Database -- `DB_TYPE` - Database type (mysql, postgresql) -- `DB_URL` - Database connection URL (default: MySQL) -- `DB_DRIVER` - JDBC driver class name (default: `com.mysql.cj.jdbc.Driver`) -- `DB_USERNAME` - Database username (default: `root`) -- `DB_PASSWORD` - Database password - -### Security -- `JWT_SECRET` - JWT secret key (change in production!) -- `JWT_EXPIRATION` - JWT expiration time in milliseconds - -### Logging -- `LOG_LEVEL_ROOT` - Root log level -- `LOG_LEVEL_WEB` - Web log level -- `LOG_LEVEL_HIBERNATE` - Hibernate log level -- `LOG_LEVEL_APP` - Application log level - -See `.env.example` for all available configuration options. - -## Branch Strategy - -This repository uses `main` as the default branch. - -## License - -[Add your license here] - diff --git a/apps/scoop-hackathon/SETUP_MYSQL.md b/apps/scoop-hackathon/SETUP_MYSQL.md deleted file mode 100644 index 6e986c4..0000000 --- a/apps/scoop-hackathon/SETUP_MYSQL.md +++ /dev/null @@ -1,77 +0,0 @@ -# MySQL Setup Instructions - -## Quick Setup - -1. **Make sure MySQL is installed and running:** - ```bash - mysql --version - # or - brew services list | grep mysql # on macOS - ``` - -2. **Update your `.env` file with your MySQL password:** - ```bash - # Edit .env file - DB_PASSWORD=your_actual_mysql_password - ``` - -3. **If MySQL root user has no password:** - - You can leave `DB_PASSWORD=` empty in `.env` - - But you may need to configure MySQL to allow passwordless login - - Or create a new MySQL user with a password - -## Creating a MySQL User (Recommended) - -For better security, create a dedicated user: - -```sql --- Connect to MySQL -mysql -u root -p - --- Create database -CREATE DATABASE IF NOT EXISTS scoop_hackathon; - --- Create user -CREATE USER 'scoop_user'@'localhost' IDENTIFIED BY 'your_secure_password'; - --- Grant privileges -GRANT ALL PRIVILEGES ON scoop_hackathon.* TO 'scoop_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -Then update your `.env` file: -``` -DB_USERNAME=scoop_user -DB_PASSWORD=your_secure_password -``` - -## Troubleshooting - -### Error: "Access denied for user 'root'@'localhost'" - -This means: -1. MySQL password is incorrect, OR -2. MySQL password is not set in `.env` file, OR -3. MySQL user doesn't have proper permissions - -**Solution:** -- Check your `.env` file has `DB_PASSWORD=your_password` -- Verify MySQL is running: `mysql -u root -p` -- If you forgot the password, reset it or create a new user - -### Error: "Unknown database 'scoop_hackathon'" - -The application will auto-create the database if `createDatabaseIfNotExist=true` is in the URL. -If it still fails, create it manually: -```sql -CREATE DATABASE scoop_hackathon; -``` - -## Testing Connection - -Test your MySQL connection: -```bash -mysql -u root -p -e "SHOW DATABASES;" -``` - -Replace `root` with your username from `.env` if different. diff --git a/apps/scoop-hackathon/TROUBLESHOOTING_DATABASE.md b/apps/scoop-hackathon/TROUBLESHOOTING_DATABASE.md deleted file mode 100644 index 81af9fc..0000000 --- a/apps/scoop-hackathon/TROUBLESHOOTING_DATABASE.md +++ /dev/null @@ -1,269 +0,0 @@ -# Troubleshooting Database Connection Issues - -## Error: "Unable to commit against JDBC Connection" - -This error typically indicates a database connection or transaction issue. Follow these steps to diagnose and fix the problem. - ---- - -## Step 1: Verify MySQL is Running - -### Check if MySQL is running: -```bash -# On macOS -brew services list | grep mysql - -# Or check the process -ps aux | grep mysql - -# Or try to connect -mysql -u root -p -``` - -### Start MySQL if it's not running: -```bash -# On macOS with Homebrew -brew services start mysql - -# Or -mysql.server start -``` - ---- - -## Step 2: Verify Database Connection Settings - -Check your `application.properties` or environment variables: - -```properties -spring.datasource.url=jdbc:mysql://localhost:3306/gitguard_agent?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true -spring.datasource.username=root -spring.datasource.password=your_password_here -``` - -**Important:** Make sure: -- The database name is correct: `gitguard_agent` -- The username is correct (default: `root`) -- The password is correct (or empty if no password is set) -- MySQL is running on port 3306 - ---- - -## Step 3: Test Database Connection Manually - -```bash -# Connect to MySQL -mysql -u root -p - -# Or if no password -mysql -u root -``` - -Once connected, verify the database exists: -```sql -SHOW DATABASES; -``` - -If `gitguard_agent` doesn't exist, create it: -```sql -CREATE DATABASE gitguard_agent CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -USE gitguard_agent; -``` - ---- - -## Step 4: Check if Tables Exist - -After connecting to MySQL: -```sql -USE gitguard_agent; -SHOW TABLES; -``` - -You should see tables like: -- `user` -- `account` -- `session` -- `authentication_history` -- etc. - -If tables don't exist, Flyway migrations haven't run. Check the application logs for Flyway errors. - ---- - -## Step 5: Check Application Logs - -Look for database connection errors in your application logs: - -```bash -# If running with Maven -mvn spring-boot:run - -# Check for errors like: -# - "Communications link failure" -# - "Access denied for user" -# - "Unknown database" -# - "Connection refused" -``` - ---- - -## Step 6: Verify Flyway Migrations - -Check if Flyway migrations ran successfully. Look for messages like: -``` -Flyway Migrations: Successfully applied X migration(s) -``` - -If migrations failed, you may need to: -1. Check the migration files in `src/main/resources/db/migration/` -2. Manually run the SQL scripts if needed -3. Check the `flyway_schema_history` table - ---- - -## Step 7: Common Solutions - -### Solution 1: Database Doesn't Exist -```sql -CREATE DATABASE gitguard_agent CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -``` - -### Solution 2: Wrong Password -Update your `application.properties`: -```properties -spring.datasource.password=your_actual_password -``` - -Or set environment variable: -```bash -export DB_PASSWORD=your_actual_password -``` - -### Solution 3: MySQL Not Running -```bash -# Start MySQL -brew services start mysql -# or -mysql.server start -``` - -### Solution 4: Port Conflict -Check if MySQL is running on a different port: -```bash -# Check MySQL port -mysql -u root -p -e "SHOW VARIABLES LIKE 'port';" -``` - -Update `application.properties` if needed: -```properties -spring.datasource.url=jdbc:mysql://localhost:YOUR_PORT/gitguard_agent?... -``` - -### Solution 5: Connection Pool Issues -If you're getting connection pool errors, try restarting the application and ensure MySQL has enough connections: - -```sql --- Check max connections -SHOW VARIABLES LIKE 'max_connections'; -``` - ---- - -## Step 8: Test Connection with Simple Query - -Once connected to MySQL, test a simple query: -```sql -USE gitguard_agent; -SELECT COUNT(*) FROM user; -``` - -If this works, the database connection is fine, and the issue might be with the application's transaction management. - ---- - -## Step 9: Check Hibernate Configuration - -The issue might be related to the Hibernate autocommit setting. Check `application.properties`: - -```properties -# This setting can sometimes cause issues -spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true -``` - -If you continue to have issues, you can try temporarily disabling this: -```properties -# Comment out or set to false -# spring.jpa.properties.hibernate.connection.provider_disables_autocommit=false -``` - ---- - -## Step 10: Restart Everything - -Sometimes a simple restart fixes connection issues: - -1. **Stop the application** (Ctrl+C) -2. **Restart MySQL:** - ```bash - brew services restart mysql - ``` -3. **Restart the application:** - ```bash - mvn spring-boot:run - ``` - ---- - -## Quick Diagnostic Commands - -```bash -# 1. Check MySQL status -brew services list | grep mysql - -# 2. Test MySQL connection -mysql -u root -p -e "SELECT 1;" - -# 3. Check if database exists -mysql -u root -p -e "SHOW DATABASES LIKE 'gitguard_agent';" - -# 4. Check if tables exist -mysql -u root -p gitguard_agent -e "SHOW TABLES;" - -# 5. Check application logs for database errors -# (Look in your console output or log files) -``` - ---- - -## Still Having Issues? - -1. **Check the full error stack trace** in your application logs -2. **Verify your MySQL version** is compatible (MySQL 5.7+ or MySQL 8.0+) -3. **Check firewall settings** if MySQL is on a remote server -4. **Review the application startup logs** for any initialization errors - ---- - -## Environment Variables - -If you're using environment variables, make sure they're set: - -```bash -# Check current environment variables -echo $DB_URL -echo $DB_USERNAME -echo $DB_PASSWORD - -# Set them if needed -export DB_URL="jdbc:mysql://localhost:3306/gitguard_agent?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true" -export DB_USERNAME="root" -export DB_PASSWORD="your_password" -``` - ---- - -## Need More Help? - -Check the application logs for the full stack trace. The improved error handling will now provide more specific error messages to help diagnose the issue. - diff --git a/apps/scoop-hackathon/fix_flyway.sh b/apps/scoop-hackathon/fix_flyway.sh deleted file mode 100755 index 5e5bbf7..0000000 --- a/apps/scoop-hackathon/fix_flyway.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash -# Script to fix Flyway failed migrations - -echo "==========================================" -echo "Flyway Migration Repair Script" -echo "==========================================" - -# Read database credentials from .env file -if [ -f .env ]; then - source .env - DB_USER=${DB_USERNAME:-root} - DB_PASS=${DB_PASSWORD:-} - - # Extract database name from DB_URL (format: jdbc:mysql://host:port/dbname?params) - if [[ $DB_URL == *"jdbc:mysql://"* ]]; then - DB_NAME=$(echo $DB_URL | sed -n 's/.*\/\([^?]*\).*/\1/p') - else - DB_NAME="gitguard_agent" - fi - - echo "Database: $DB_NAME" - echo "User: $DB_USER" - echo "" - - # Build MySQL command - if [ -z "$DB_PASS" ]; then - echo "Attempting to connect without password..." - MYSQL_CMD="mysql -u $DB_USER" - else - MYSQL_CMD="mysql -u $DB_USER -p$DB_PASS" - fi - - echo "Repairing Flyway migration..." - - # Try to execute the repair SQL - if $MYSQL_CMD $DB_NAME -e "DELETE FROM flyway_schema_history WHERE success = 0;" 2>/dev/null; then - FAILED_COUNT=$($MYSQL_CMD $DB_NAME -N -e "SELECT COUNT(*) FROM flyway_schema_history WHERE success = 0;" 2>/dev/null) - if [ "$FAILED_COUNT" = "0" ] || [ -z "$FAILED_COUNT" ]; then - echo "✓ Successfully removed failed migration records" - echo "✓ You can now restart the application" - else - echo "⚠ Warning: Some failed migrations may still exist" - fi - else - echo "" - echo "⚠ Could not connect automatically. Please run manually:" - echo "" - echo " mysql -u $DB_USER -p $DB_NAME" - echo "" - echo "Then execute:" - echo " DELETE FROM flyway_schema_history WHERE success = 0;" - echo "" - exit 1 - fi -else - echo "Error: .env file not found" - echo "Please create a .env file with your database credentials" - exit 1 -fi diff --git a/apps/scoop-hackathon/fix_flyway.sql b/apps/scoop-hackathon/fix_flyway.sql deleted file mode 100644 index b65ecea..0000000 --- a/apps/scoop-hackathon/fix_flyway.sql +++ /dev/null @@ -1,20 +0,0 @@ --- SQL script to fix Flyway failed migrations --- Run this in MySQL: mysql -u root -p gitguard_agent < fix_flyway.sql - --- Option 1: Delete only failed migrations -DELETE FROM flyway_schema_history WHERE success = 0; - --- Option 2: Delete all migration history (use if you want to start fresh) --- DELETE FROM flyway_schema_history; - --- Option 3: Drop all tables and start completely fresh --- DROP TABLE IF EXISTS trace; --- DROP TABLE IF EXISTS plan; --- DROP TABLE IF EXISTS snapshot; --- DROP TABLE IF EXISTS event; --- DROP TABLE IF EXISTS git_session; --- DROP TABLE IF EXISTS session; --- DROP TABLE IF EXISTS account; --- DROP TABLE IF EXISTS verification_token; --- DROP TABLE IF EXISTS `user`; --- DROP TABLE IF EXISTS flyway_schema_history; diff --git a/apps/scoop-hackathon/pom.xml b/apps/scoop-hackathon/pom.xml deleted file mode 100644 index e7f0dbb..0000000 --- a/apps/scoop-hackathon/pom.xml +++ /dev/null @@ -1,215 +0,0 @@ - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 3.2.0 - - - - com.scoop - scoop-hackathon - 1.0.0 - Scoop Hackathon - Spring Boot application for Scoop Hackathon - - - 21 - 21 - 21 - UTF-8 - UTF-8 - - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - - org.springframework.boot - spring-boot-starter-validation - - - - - org.springframework.boot - spring-boot-starter-security - - - - - org.postgresql - postgresql - runtime - - - - - com.mysql - mysql-connector-j - runtime - - - - - org.projectlombok - lombok - true - - - - - org.flywaydb - flyway-core - 10.10.0 - - - - - org.flywaydb - flyway-mysql - 10.10.0 - - - - - org.flywaydb - flyway-database-postgresql - 10.10.0 - - - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - org.springframework.security - spring-security-test - test - - - - - com.fasterxml.jackson.core - jackson-databind - - - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - - - io.github.cdimascio - dotenv-java - 3.0.0 - - - - - io.jsonwebtoken - jjwt-api - 0.12.3 - - - io.jsonwebtoken - jjwt-impl - 0.12.3 - runtime - - - io.jsonwebtoken - jjwt-jackson - 0.12.3 - runtime - - - - - org.springframework.security - spring-security-crypto - - - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.3.0 - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 21 - 21 - - - - - - org.flywaydb - flyway-maven-plugin - - ${spring.datasource.url} - ${spring.datasource.username} - ${spring.datasource.password} - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.2.2 - - - - diff --git a/apps/scoop-hackathon/quick_fix_flyway.sh b/apps/scoop-hackathon/quick_fix_flyway.sh deleted file mode 100755 index df5e33d..0000000 --- a/apps/scoop-hackathon/quick_fix_flyway.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# Quick fix for Flyway failed migrations -# Run this script to repair the database, then restart your application - -echo "==========================================" -echo "Quick Flyway Repair" -echo "==========================================" -echo "" -echo "This will delete failed migration records from the database." -echo "" - -# Default values -DB_NAME="gitguard_agent" -DB_USER="root" - -# Try to read from .env if it exists -if [ -f .env ]; then - source .env - if [ ! -z "$DB_URL" ]; then - # Extract database name from DB_URL - DB_NAME=$(echo $DB_URL | sed -n 's/.*\/\([^?]*\).*/\1/p') - fi - DB_USER=${DB_USERNAME:-root} -fi - -echo "Database: $DB_NAME" -echo "User: $DB_USER" -echo "" -echo "Please enter your MySQL password when prompted:" -echo "" - -# Run the repair SQL -mysql -u "$DB_USER" -p "$DB_NAME" << EOF -DELETE FROM flyway_schema_history WHERE success = 0; -SELECT 'Repair complete!' AS message; -EOF - -if [ $? -eq 0 ]; then - echo "" - echo "✓ Repair successful!" - echo "✓ You can now restart your Spring Boot application" -else - echo "" - echo "✗ Repair failed. Please check your MySQL credentials." - echo "" - echo "You can also run this SQL manually:" - echo " mysql -u $DB_USER -p $DB_NAME" - echo " DELETE FROM flyway_schema_history WHERE success = 0;" -fi - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/ScoopHackathonApplication.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/ScoopHackathonApplication.java deleted file mode 100644 index f540dd9..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/ScoopHackathonApplication.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.scoop.hackathon; - -import io.github.cdimascio.dotenv.Dotenv; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; - -@SpringBootApplication -@EnableJpaRepositories(basePackages = "com.scoop.hackathon.repository") -public class ScoopHackathonApplication { - - public static void main(String[] args) { - // Load .env file before Spring Boot starts - loadEnvironmentVariables(); - - SpringApplication.run(ScoopHackathonApplication.class, args); - } - - private static void loadEnvironmentVariables() { - try { - Dotenv dotenv = Dotenv.configure() - .ignoreIfMissing() - .load(); - - // Load .env file variables into system properties - // This allows Spring Boot to access them via ${VAR_NAME} syntax - dotenv.entries().forEach(entry -> { - String key = entry.getKey(); - String value = entry.getValue(); - if (System.getProperty(key) == null) { - System.setProperty(key, value != null ? value : ""); - } - }); - - // Check if MySQL password is set - String dbPassword = System.getProperty("DB_PASSWORD"); - if (dbPassword == null || dbPassword.trim().isEmpty()) { - System.err.println("⚠ WARNING: DB_PASSWORD is not set in .env file!"); - System.err.println(" Please update your .env file with your MySQL password:"); - System.err.println(" DB_PASSWORD=your_mysql_password"); - System.err.println(" If MySQL has no password, you can leave it empty but ensure MySQL allows passwordless login."); - } - - System.out.println("✓ Environment variables loaded from .env file"); - } catch (Exception e) { - System.err.println("⚠ Warning: Could not load .env file: " + e.getMessage()); - System.err.println(" Make sure .env file exists in the project root directory"); - System.err.println(" You can copy .env.example to .env as a starting point"); - } - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/DotEnvConfig.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/DotEnvConfig.java deleted file mode 100644 index 0c2a7a5..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/DotEnvConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.scoop.hackathon.config; - -import io.github.cdimascio.dotenv.Dotenv; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class DotEnvConfig { - - @Bean - public Dotenv dotenv() { - return Dotenv.configure() - .ignoreIfMissing() - .load(); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/EnvironmentConfig.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/EnvironmentConfig.java deleted file mode 100644 index c64014c..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/EnvironmentConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.scoop.hackathon.config; - -import io.github.cdimascio.dotenv.Dotenv; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Configuration; - -import jakarta.annotation.PostConstruct; - -@Configuration -public class EnvironmentConfig { - - private static final Logger logger = LoggerFactory.getLogger(EnvironmentConfig.class); - - private final Dotenv dotenv; - - public EnvironmentConfig(Dotenv dotenv) { - this.dotenv = dotenv; - } - - @PostConstruct - public void logEnvironmentVariables() { - // Log that environment variables were loaded (they're already loaded in main method) - logger.info("Environment configuration initialized"); - logger.debug("Total environment variables loaded: {}", dotenv.entries().size()); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/FlywayRepairComponent.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/FlywayRepairComponent.java deleted file mode 100644 index 771cbd6..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/FlywayRepairComponent.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.scoop.hackathon.config; - -import jakarta.annotation.PostConstruct; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.annotation.Order; -import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; - -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -/** - * Component that repairs failed Flyway migrations before Flyway initializes. - * This implements InitializingBean to ensure it runs as early as possible. - */ -@Component -@Order(1) -public class FlywayRepairComponent implements InitializingBean { - - private static final Logger logger = LoggerFactory.getLogger(FlywayRepairComponent.class); - - @Nullable - private final DataSource dataSource; - - public FlywayRepairComponent(@Nullable DataSource dataSource) { - this.dataSource = dataSource; - } - - @Override - public void afterPropertiesSet() throws Exception { - repairFailedMigrations(); - } - - @PostConstruct - public void repairFailedMigrations() { - if (dataSource == null) { - logger.debug("No DataSource found, skipping Flyway repair"); - return; - } - - logger.info("Checking for failed Flyway migrations..."); - - try (Connection connection = dataSource.getConnection()) { - // Check if flyway_schema_history table exists - if (!tableExists(connection, "flyway_schema_history")) { - logger.debug("flyway_schema_history table does not exist, skipping repair"); - return; - } - - // Check for failed migrations - int failedCount = countFailedMigrations(connection); - - if (failedCount > 0) { - logger.warn("Found {} failed migration(s). Attempting to repair...", failedCount); - - // Delete failed migration records - int deleted = deleteFailedMigrations(connection); - - if (deleted > 0) { - logger.info("✓ Successfully repaired {} failed migration record(s)", deleted); - logger.info(" Application will continue with migration..."); - } else { - logger.warn("Failed to delete failed migration records"); - } - } else { - logger.debug("No failed migrations found"); - } - - } catch (SQLException e) { - logger.error("Error while repairing Flyway migrations: {}", e.getMessage(), e); - // Don't throw exception - let Flyway handle it, but log the error - } - } - - private boolean tableExists(Connection connection, String tableName) throws SQLException { - String sql = "SELECT COUNT(*) FROM information_schema.tables " + - "WHERE table_schema = DATABASE() AND table_name = ?"; - - try (PreparedStatement stmt = connection.prepareStatement(sql)) { - stmt.setString(1, tableName); - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - return rs.getInt(1) > 0; - } - } - } - return false; - } - - private int countFailedMigrations(Connection connection) throws SQLException { - String sql = "SELECT COUNT(*) FROM flyway_schema_history WHERE success = 0"; - - try (PreparedStatement stmt = connection.prepareStatement(sql); - ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - return rs.getInt(1); - } - } - return 0; - } - - private int deleteFailedMigrations(Connection connection) throws SQLException { - String sql = "DELETE FROM flyway_schema_history WHERE success = 0"; - - try (PreparedStatement stmt = connection.prepareStatement(sql)) { - return stmt.executeUpdate(); - } - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/FlywayRepairConfig.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/FlywayRepairConfig.java deleted file mode 100644 index 47d90b6..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/FlywayRepairConfig.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.scoop.hackathon.config; - -import jakarta.annotation.PostConstruct; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.lang.Nullable; - -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -/** - * Automatically repairs failed Flyway migrations on application startup. - * This component runs before FlywayAutoConfiguration to ensure failed migrations - * are cleaned up before Flyway checks for them. - * - * Uses @PostConstruct with @Order to run early in the Spring Boot lifecycle, - * before Flyway's MigrationInitializer executes. - */ -@Configuration -@Order(1) // Run before Flyway auto-configuration (which is typically Order(2147483647)) -public class FlywayRepairConfig { - - private static final Logger logger = LoggerFactory.getLogger(FlywayRepairConfig.class); - - @Nullable - private final DataSource dataSource; - - public FlywayRepairConfig(@Nullable DataSource dataSource) { - this.dataSource = dataSource; - } - - @PostConstruct - public void repairFailedMigrations() { - if (dataSource == null) { - logger.debug("No DataSource found, skipping Flyway repair"); - return; - } - - logger.info("Checking for failed Flyway migrations..."); - - try (Connection connection = dataSource.getConnection()) { - // Check if flyway_schema_history table exists - if (!tableExists(connection, "flyway_schema_history")) { - logger.debug("flyway_schema_history table does not exist, skipping repair"); - return; - } - - // Check for failed migrations - int failedCount = countFailedMigrations(connection); - - if (failedCount > 0) { - logger.warn("Found {} failed migration(s). Attempting to repair...", failedCount); - - // Delete failed migration records - int deleted = deleteFailedMigrations(connection); - - if (deleted > 0) { - logger.info("✓ Successfully repaired {} failed migration record(s)", deleted); - logger.info(" Application will continue with migration..."); - } else { - logger.warn("Failed to delete failed migration records"); - } - } else { - logger.debug("No failed migrations found"); - } - - } catch (SQLException e) { - logger.error("Error while repairing Flyway migrations: {}", e.getMessage(), e); - // Don't throw exception - let Flyway handle it, but log the error - } - } - - private boolean tableExists(Connection connection, String tableName) throws SQLException { - String sql = "SELECT COUNT(*) FROM information_schema.tables " + - "WHERE table_schema = DATABASE() AND table_name = ?"; - - try (PreparedStatement stmt = connection.prepareStatement(sql)) { - stmt.setString(1, tableName); - try (ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - return rs.getInt(1) > 0; - } - } - } - return false; - } - - private int countFailedMigrations(Connection connection) throws SQLException { - String sql = "SELECT COUNT(*) FROM flyway_schema_history WHERE success = 0"; - - try (PreparedStatement stmt = connection.prepareStatement(sql); - ResultSet rs = stmt.executeQuery()) { - if (rs.next()) { - return rs.getInt(1); - } - } - return 0; - } - - private int deleteFailedMigrations(Connection connection) throws SQLException { - String sql = "DELETE FROM flyway_schema_history WHERE success = 0"; - - try (PreparedStatement stmt = connection.prepareStatement(sql)) { - return stmt.executeUpdate(); - } - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/OpenApiConfig.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/OpenApiConfig.java deleted file mode 100644 index 9483f7d..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/OpenApiConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.scoop.hackathon.config; - -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Contact; -import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.info.License; -import io.swagger.v3.oas.models.security.SecurityRequirement; -import io.swagger.v3.oas.models.security.SecurityScheme; -import io.swagger.v3.oas.models.servers.Server; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.util.List; - -@Configuration -public class OpenApiConfig { - - @Value("${server.port:8080}") - private String serverPort; - - @Bean - public OpenAPI customOpenAPI() { - final String securitySchemeName = "bearerAuth"; - - return new OpenAPI() - .info(new Info() - .title("Scoop Hackathon API") - .version("1.0.0") - .description("RESTful API documentation for Scoop Hackathon application. " + - "This API provides endpoints for user management, authentication, and various " + - "application features. Use the 'Authorize' button to authenticate with JWT tokens.") - .contact(new Contact() - .name("Scoop Hackathon Team") - .email("support@scoop-hackathon.com")) - .license(new License() - .name("Apache 2.0") - .url("https://www.apache.org/licenses/LICENSE-2.0.html"))) - .servers(List.of( - new Server() - .url("http://localhost:" + serverPort) - .description("Local Development Server"), - new Server() - .url("https://api.scoop-hackathon.com") - .description("Production Server"))) - .addSecurityItem(new SecurityRequirement() - .addList(securitySchemeName)) - .components(new Components() - .addSecuritySchemes(securitySchemeName, - new SecurityScheme() - .name(securitySchemeName) - .type(SecurityScheme.Type.HTTP) - .scheme("bearer") - .bearerFormat("JWT") - .description("JWT token obtained from /api/auth/login endpoint. " + - "Format: Bearer {token}"))); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/SecurityConfig.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/SecurityConfig.java deleted file mode 100644 index e69ef64..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/config/SecurityConfig.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.scoop.hackathon.config; - -import com.scoop.hackathon.security.JwtAuthenticationFilter; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - -@Configuration -@EnableWebSecurity -@EnableMethodSecurity -public class SecurityConfig { - - private final JwtAuthenticationFilter jwtAuthenticationFilter; - - public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { - this.jwtAuthenticationFilter = jwtAuthenticationFilter; - } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { - return authConfig.getAuthenticationManager(); - } - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .csrf(csrf -> csrf.disable()) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth - // Swagger/OpenAPI endpoints - must be before /api/** to allow unauthenticated access - .requestMatchers( - "/swagger-ui/**", - "/swagger-ui.html", - "/v3/api-docs/**", - "/swagger-resources/**", - "/webjars/**", - "/swagger-ui.html/**", - "/api-docs/**" - ).permitAll() - // Authentication endpoints - .requestMatchers("/api/auth/register", "/api/auth/login", "/api/auth/validate", "/api/auth/signup/client").permitAll() - // H2 Console (for development) - .requestMatchers("/h2-console/**").permitAll() - // All other API endpoints require authentication - .requestMatchers("/api/**").authenticated() - .anyRequest().authenticated() - ) - .headers(headers -> headers.frameOptions(frameOptions -> frameOptions.disable())) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AccountController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AccountController.java deleted file mode 100644 index 859b335..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AccountController.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.AccountDto; -import com.scoop.hackathon.dto.CreateAccountRequest; -import com.scoop.hackathon.dto.UpdateAccountRequest; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.AccountService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/accounts") -public class AccountController { - - private final AccountService accountService; - - public AccountController(AccountService accountService) { - this.accountService = accountService; - } - - @PostMapping - public ResponseEntity createAccount(@Valid @RequestBody CreateAccountRequest request) { - AccountDto account = accountService.createAccount(request); - return new ResponseEntity<>(account, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getAccountById(@PathVariable String id) { - AccountDto account = accountService.getAccountById(id); - return ResponseEntity.ok(account); - } - - @GetMapping - public ResponseEntity> getAllAccounts() { - List accounts = accountService.getAllAccounts(); - return ResponseEntity.ok(accounts); - } - - @GetMapping("/user/{userId}") - public ResponseEntity> getAccountsByUserId(@PathVariable String userId) { - List accounts = accountService.getAccountsByUserId(userId); - return ResponseEntity.ok(accounts); - } - - @GetMapping("/provider/{provider}/account/{providerAccountId}") - public ResponseEntity getAccountByProviderAndProviderAccountId( - @PathVariable String provider, - @PathVariable String providerAccountId) { - AccountDto account = accountService.getAccountByProviderAndProviderAccountId(provider, providerAccountId); - return ResponseEntity.ok(account); - } - - @PutMapping("/{id}") - public ResponseEntity updateAccount( - @PathVariable String id, - @Valid @RequestBody UpdateAccountRequest request) { - AccountDto account = accountService.updateAccount(id, request); - return ResponseEntity.ok(account); - } - - @DeleteMapping("/{id}") - public ResponseEntity deleteAccount(@PathVariable String id) { - accountService.deleteAccount(id); - ApiResponse response = new ApiResponse(true, "Account deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AuthController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AuthController.java deleted file mode 100644 index 9f22888..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AuthController.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.AuthResponse; -import com.scoop.hackathon.dto.AuthenticationHistoryDto; -import com.scoop.hackathon.dto.LoginRequest; -import com.scoop.hackathon.dto.RegisterRequest; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.security.JwtUtil; -import com.scoop.hackathon.service.AuthenticationService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/auth") -@Tag(name = "Authentication", description = "Authentication and authorization endpoints for user registration, login, and JWT token management") -public class AuthController { - - private final AuthenticationService authenticationService; - private final JwtUtil jwtUtil; - - public AuthController(AuthenticationService authenticationService, JwtUtil jwtUtil) { - this.authenticationService = authenticationService; - this.jwtUtil = jwtUtil; - } - - @Operation( - summary = "Register a new user", - description = "Creates a new user account with the provided information. Returns a JWT token upon successful registration." - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "User successfully registered", - content = @Content(schema = @Schema(implementation = AuthResponse.class))), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid input or email already exists"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "Internal server error") - }) - @PostMapping("/register") - public ResponseEntity register( - @Valid @RequestBody RegisterRequest request, - HttpServletRequest httpRequest) { - AuthResponse response = authenticationService.register(request, httpRequest); - return new ResponseEntity<>(response, HttpStatus.CREATED); - } - - @Operation( - summary = "User login", - description = "Authenticates a user with email and password. Returns a JWT token upon successful authentication." - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Login successful", - content = @Content(schema = @Schema(implementation = AuthResponse.class))), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Invalid credentials"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "User not found"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "Internal server error") - }) - @PostMapping("/login") - public ResponseEntity login( - @Valid @RequestBody LoginRequest request, - HttpServletRequest httpRequest) { - AuthResponse response = authenticationService.login(request, httpRequest); - return ResponseEntity.ok(response); - } - - @Operation( - summary = "Sign up a new client", - description = "Creates a new client account with the provided information. Returns a JWT token upon successful signup." - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "Client successfully signed up", - content = @Content(schema = @Schema(implementation = AuthResponse.class))), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid input or email already exists"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "Internal server error") - }) - @PostMapping("/signup/client") - public ResponseEntity signupClient( - @Valid @RequestBody RegisterRequest request, - HttpServletRequest httpRequest) { - AuthResponse response = authenticationService.signupClient(request, httpRequest); - return new ResponseEntity<>(response, HttpStatus.CREATED); - } - - @Operation( - summary = "Validate JWT token", - description = "Validates a JWT token and records the validation attempt in authentication history." - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Token is valid", - content = @Content(schema = @Schema(implementation = com.scoop.hackathon.payload.ApiResponse.class))), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid or expired token"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Unauthorized - Invalid authorization header") - }) - @PostMapping("/validate") - public ResponseEntity validateToken( - @Parameter(description = "Bearer token in Authorization header", required = true) - @RequestHeader("Authorization") String authHeader, - HttpServletRequest httpRequest) { - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - throw new IllegalArgumentException("Invalid authorization header"); - } - - String token = authHeader.substring(7); - authenticationService.validateToken(token, httpRequest); - - ApiResponse response = new ApiResponse(true, "Token is valid"); - return ResponseEntity.ok(response); - } - - @Operation( - summary = "Get current user's authentication history", - description = "Retrieves the authentication history for the currently authenticated user. Requires JWT token." - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Authentication history retrieved successfully", - content = @Content(schema = @Schema(implementation = AuthenticationHistoryDto.class))), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Unauthorized - Invalid or missing token") - }) - @SecurityRequirement(name = "bearerAuth") - @GetMapping("/history") - public ResponseEntity> getAuthHistory( - @Parameter(description = "Bearer token in Authorization header", required = true) - @RequestHeader("Authorization") String authHeader) { - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - throw new IllegalArgumentException("Invalid authorization header"); - } - - String token = authHeader.substring(7); - String userId = jwtUtil.extractUserId(token); - - List history = authenticationService.getAuthHistory(userId); - return ResponseEntity.ok(history); - } - - @Operation( - summary = "Get authentication history by user ID", - description = "Retrieves the authentication history for a specific user by user ID. Requires JWT token." - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Authentication history retrieved successfully"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Unauthorized - Invalid or missing token"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "User not found") - }) - @SecurityRequirement(name = "bearerAuth") - @GetMapping("/history/{userId}") - public ResponseEntity> getAuthHistoryByUserId( - @Parameter(description = "User ID", required = true) - @PathVariable String userId) { - List history = authenticationService.getAuthHistory(userId); - return ResponseEntity.ok(history); - } - - @Operation( - summary = "Get authentication history by user ID and action", - description = "Retrieves filtered authentication history for a specific user by action type (e.g., LOGIN, LOGOUT). Requires JWT token." - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Filtered authentication history retrieved successfully"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Unauthorized - Invalid or missing token"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "User not found") - }) - @SecurityRequirement(name = "bearerAuth") - @GetMapping("/history/{userId}/action/{action}") - public ResponseEntity> getAuthHistoryByAction( - @Parameter(description = "User ID", required = true) - @PathVariable String userId, - @Parameter(description = "Action type (e.g., LOGIN, LOGOUT, TOKEN_VALIDATED)", required = true) - @PathVariable String action) { - List history = authenticationService.getAuthHistoryByAction(userId, action); - return ResponseEntity.ok(history); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AuthenticationHistoryController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AuthenticationHistoryController.java deleted file mode 100644 index 131c5cc..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/AuthenticationHistoryController.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.AuthenticationHistoryDto; -import com.scoop.hackathon.dto.CreateAuthenticationHistoryRequest; -import com.scoop.hackathon.dto.UpdateAuthenticationHistoryRequest; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.AuthenticationHistoryService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/authentication-history") -public class AuthenticationHistoryController { - - private final AuthenticationHistoryService authenticationHistoryService; - - public AuthenticationHistoryController(AuthenticationHistoryService authenticationHistoryService) { - this.authenticationHistoryService = authenticationHistoryService; - } - - @PostMapping - public ResponseEntity createAuthenticationHistory( - @Valid @RequestBody CreateAuthenticationHistoryRequest request) { - AuthenticationHistoryDto history = authenticationHistoryService.createAuthenticationHistory(request); - return new ResponseEntity<>(history, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getAuthenticationHistoryById(@PathVariable String id) { - AuthenticationHistoryDto history = authenticationHistoryService.getAuthenticationHistoryById(id); - return ResponseEntity.ok(history); - } - - @GetMapping - public ResponseEntity> getAllAuthenticationHistory() { - List history = authenticationHistoryService.getAllAuthenticationHistory(); - return ResponseEntity.ok(history); - } - - @GetMapping("/user/{userId}") - public ResponseEntity> getAuthenticationHistoryByUserId(@PathVariable String userId) { - List history = authenticationHistoryService.getAuthenticationHistoryByUserId(userId); - return ResponseEntity.ok(history); - } - - @GetMapping("/user/{userId}/action/{action}") - public ResponseEntity> getAuthenticationHistoryByUserIdAndAction( - @PathVariable String userId, - @PathVariable String action) { - List history = authenticationHistoryService.getAuthenticationHistoryByUserIdAndAction(userId, action); - return ResponseEntity.ok(history); - } - - @GetMapping("/user/{userId}/success/{success}") - public ResponseEntity> getAuthenticationHistoryByUserIdAndSuccess( - @PathVariable String userId, - @PathVariable Boolean success) { - List history = authenticationHistoryService.getAuthenticationHistoryByUserIdAndSuccess(userId, success); - return ResponseEntity.ok(history); - } - - @GetMapping("/token/{tokenId}") - public ResponseEntity> getAuthenticationHistoryByTokenId(@PathVariable String tokenId) { - List history = authenticationHistoryService.getAuthenticationHistoryByTokenId(tokenId); - return ResponseEntity.ok(history); - } - - @PutMapping("/{id}") - public ResponseEntity updateAuthenticationHistory( - @PathVariable String id, - @Valid @RequestBody UpdateAuthenticationHistoryRequest request) { - AuthenticationHistoryDto history = authenticationHistoryService.updateAuthenticationHistory(id, request); - return ResponseEntity.ok(history); - } - - @DeleteMapping("/{id}") - public ResponseEntity deleteAuthenticationHistory(@PathVariable String id) { - authenticationHistoryService.deleteAuthenticationHistory(id); - ApiResponse response = new ApiResponse(true, "AuthenticationHistory deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/EventController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/EventController.java deleted file mode 100644 index 13da03d..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/EventController.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.CreateEventRequest; -import com.scoop.hackathon.dto.EventDto; -import com.scoop.hackathon.dto.UpdateEventRequest; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.EventService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/events") -public class EventController { - - private final EventService eventService; - - public EventController(EventService eventService) { - this.eventService = eventService; - } - - @PostMapping - public ResponseEntity createEvent(@Valid @RequestBody CreateEventRequest request) { - EventDto event = eventService.createEvent(request); - return new ResponseEntity<>(event, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getEventById(@PathVariable String id) { - EventDto event = eventService.getEventById(id); - return ResponseEntity.ok(event); - } - - @GetMapping - public ResponseEntity> getAllEvents() { - List events = eventService.getAllEvents(); - return ResponseEntity.ok(events); - } - - @GetMapping("/user/{userId}") - public ResponseEntity> getEventsByUserId(@PathVariable String userId) { - List events = eventService.getEventsByUserId(userId); - return ResponseEntity.ok(events); - } - - @GetMapping("/git-session/{gitSessionId}") - public ResponseEntity> getEventsByGitSessionId(@PathVariable String gitSessionId) { - List events = eventService.getEventsByGitSessionId(gitSessionId); - return ResponseEntity.ok(events); - } - - @GetMapping("/type/{type}") - public ResponseEntity> getEventsByType(@PathVariable String type) { - List events = eventService.getEventsByType(type); - return ResponseEntity.ok(events); - } - - @PutMapping("/{id}") - public ResponseEntity updateEvent( - @PathVariable String id, - @Valid @RequestBody UpdateEventRequest request) { - EventDto event = eventService.updateEvent(id, request); - return ResponseEntity.ok(event); - } - - @DeleteMapping("/{id}") - public ResponseEntity deleteEvent(@PathVariable String id) { - eventService.deleteEvent(id); - ApiResponse response = new ApiResponse(true, "Event deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/GitSessionController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/GitSessionController.java deleted file mode 100644 index 987a05f..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/GitSessionController.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.CreateGitSessionRequest; -import com.scoop.hackathon.dto.GitSessionDto; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.GitSessionService; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/git-sessions") -public class GitSessionController { - - private final GitSessionService gitSessionService; - - public GitSessionController(GitSessionService gitSessionService) { - this.gitSessionService = gitSessionService; - } - - @PostMapping - public ResponseEntity createGitSession(@RequestBody CreateGitSessionRequest request) { - GitSessionDto gitSession = gitSessionService.createGitSession(request); - return new ResponseEntity<>(gitSession, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getGitSessionById(@PathVariable String id) { - GitSessionDto gitSession = gitSessionService.getGitSessionById(id); - return ResponseEntity.ok(gitSession); - } - - @GetMapping - public ResponseEntity> getAllGitSessions() { - List gitSessions = gitSessionService.getAllGitSessions(); - return ResponseEntity.ok(gitSessions); - } - - @GetMapping("/user/{userId}") - public ResponseEntity> getGitSessionsByUserId(@PathVariable String userId) { - List gitSessions = gitSessionService.getGitSessionsByUserId(userId); - return ResponseEntity.ok(gitSessions); - } - - @PutMapping("/{id}") - public ResponseEntity updateGitSession( - @PathVariable String id, - @RequestBody CreateGitSessionRequest request) { - GitSessionDto gitSession = gitSessionService.updateGitSession(id, request); - return ResponseEntity.ok(gitSession); - } - - @DeleteMapping("/{id}") - public ResponseEntity deleteGitSession(@PathVariable String id) { - gitSessionService.deleteGitSession(id); - ApiResponse response = new ApiResponse(true, "GitSession deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/PlanController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/PlanController.java deleted file mode 100644 index cfedffd..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/PlanController.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.CreatePlanRequest; -import com.scoop.hackathon.dto.PlanDto; -import com.scoop.hackathon.dto.UpdatePlanRequest; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.PlanService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/plans") -public class PlanController { - - private final PlanService planService; - - public PlanController(PlanService planService) { - this.planService = planService; - } - - @PostMapping - public ResponseEntity createPlan(@Valid @RequestBody CreatePlanRequest request) { - PlanDto plan = planService.createPlan(request); - return new ResponseEntity<>(plan, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getPlanById(@PathVariable String id) { - PlanDto plan = planService.getPlanById(id); - return ResponseEntity.ok(plan); - } - - @GetMapping - public ResponseEntity> getAllPlans() { - List plans = planService.getAllPlans(); - return ResponseEntity.ok(plans); - } - - @GetMapping("/git-session/{gitSessionId}") - public ResponseEntity> getPlansByGitSessionId(@PathVariable String gitSessionId) { - List plans = planService.getPlansByGitSessionId(gitSessionId); - return ResponseEntity.ok(plans); - } - - @PutMapping("/{id}") - public ResponseEntity updatePlan( - @PathVariable String id, - @Valid @RequestBody UpdatePlanRequest request) { - PlanDto plan = planService.updatePlan(id, request); - return ResponseEntity.ok(plan); - } - - @DeleteMapping("/{id}") - public ResponseEntity deletePlan(@PathVariable String id) { - planService.deletePlan(id); - ApiResponse response = new ApiResponse(true, "Plan deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/SessionController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/SessionController.java deleted file mode 100644 index 80628c2..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/SessionController.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.CreateSessionRequest; -import com.scoop.hackathon.dto.SessionDto; -import com.scoop.hackathon.dto.UpdateSessionRequest; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.SessionService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/sessions") -public class SessionController { - - private final SessionService sessionService; - - public SessionController(SessionService sessionService) { - this.sessionService = sessionService; - } - - @PostMapping - public ResponseEntity createSession(@Valid @RequestBody CreateSessionRequest request) { - SessionDto session = sessionService.createSession(request); - return new ResponseEntity<>(session, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getSessionById(@PathVariable String id) { - SessionDto session = sessionService.getSessionById(id); - return ResponseEntity.ok(session); - } - - @GetMapping("/token/{token}") - public ResponseEntity getSessionByToken(@PathVariable String token) { - SessionDto session = sessionService.getSessionByToken(token); - return ResponseEntity.ok(session); - } - - @GetMapping - public ResponseEntity> getAllSessions() { - List sessions = sessionService.getAllSessions(); - return ResponseEntity.ok(sessions); - } - - @GetMapping("/user/{userId}") - public ResponseEntity> getSessionsByUserId(@PathVariable String userId) { - List sessions = sessionService.getSessionsByUserId(userId); - return ResponseEntity.ok(sessions); - } - - @PutMapping("/{id}") - public ResponseEntity updateSession( - @PathVariable String id, - @Valid @RequestBody UpdateSessionRequest request) { - SessionDto session = sessionService.updateSession(id, request); - return ResponseEntity.ok(session); - } - - @DeleteMapping("/{id}") - public ResponseEntity deleteSession(@PathVariable String id) { - sessionService.deleteSession(id); - ApiResponse response = new ApiResponse(true, "Session deleted successfully"); - return ResponseEntity.ok(response); - } - - @DeleteMapping("/token/{token}") - public ResponseEntity deleteSessionByToken(@PathVariable String token) { - sessionService.deleteSessionByToken(token); - ApiResponse response = new ApiResponse(true, "Session deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/SnapshotController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/SnapshotController.java deleted file mode 100644 index a878caa..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/SnapshotController.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.CreateSnapshotRequest; -import com.scoop.hackathon.dto.SnapshotDto; -import com.scoop.hackathon.dto.UpdateSnapshotRequest; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.SnapshotService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/snapshots") -public class SnapshotController { - - private final SnapshotService snapshotService; - - public SnapshotController(SnapshotService snapshotService) { - this.snapshotService = snapshotService; - } - - @PostMapping - public ResponseEntity createSnapshot(@Valid @RequestBody CreateSnapshotRequest request) { - SnapshotDto snapshot = snapshotService.createSnapshot(request); - return new ResponseEntity<>(snapshot, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getSnapshotById(@PathVariable String id) { - SnapshotDto snapshot = snapshotService.getSnapshotById(id); - return ResponseEntity.ok(snapshot); - } - - @GetMapping - public ResponseEntity> getAllSnapshots() { - List snapshots = snapshotService.getAllSnapshots(); - return ResponseEntity.ok(snapshots); - } - - @GetMapping("/git-session/{gitSessionId}") - public ResponseEntity> getSnapshotsByGitSessionId(@PathVariable String gitSessionId) { - List snapshots = snapshotService.getSnapshotsByGitSessionId(gitSessionId); - return ResponseEntity.ok(snapshots); - } - - @PutMapping("/{id}") - public ResponseEntity updateSnapshot( - @PathVariable String id, - @Valid @RequestBody UpdateSnapshotRequest request) { - SnapshotDto snapshot = snapshotService.updateSnapshot(id, request); - return ResponseEntity.ok(snapshot); - } - - @DeleteMapping("/{id}") - public ResponseEntity deleteSnapshot(@PathVariable String id) { - snapshotService.deleteSnapshot(id); - ApiResponse response = new ApiResponse(true, "Snapshot deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/TraceController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/TraceController.java deleted file mode 100644 index 29fb594..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/TraceController.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.CreateTraceRequest; -import com.scoop.hackathon.dto.TraceDto; -import com.scoop.hackathon.dto.UpdateTraceRequest; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.TraceService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/traces") -public class TraceController { - - private final TraceService traceService; - - public TraceController(TraceService traceService) { - this.traceService = traceService; - } - - @PostMapping - public ResponseEntity createTrace(@Valid @RequestBody CreateTraceRequest request) { - TraceDto trace = traceService.createTrace(request); - return new ResponseEntity<>(trace, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getTraceById(@PathVariable String id) { - TraceDto trace = traceService.getTraceById(id); - return ResponseEntity.ok(trace); - } - - @GetMapping - public ResponseEntity> getAllTraces() { - List traces = traceService.getAllTraces(); - return ResponseEntity.ok(traces); - } - - @GetMapping("/git-session/{gitSessionId}") - public ResponseEntity> getTracesByGitSessionId(@PathVariable String gitSessionId) { - List traces = traceService.getTracesByGitSessionId(gitSessionId); - return ResponseEntity.ok(traces); - } - - @GetMapping("/snapshot/{snapshotId}") - public ResponseEntity> getTracesBySnapshotId(@PathVariable String snapshotId) { - List traces = traceService.getTracesBySnapshotId(snapshotId); - return ResponseEntity.ok(traces); - } - - @PutMapping("/{id}") - public ResponseEntity updateTrace( - @PathVariable String id, - @Valid @RequestBody UpdateTraceRequest request) { - TraceDto trace = traceService.updateTrace(id, request); - return ResponseEntity.ok(trace); - } - - @DeleteMapping("/{id}") - public ResponseEntity deleteTrace(@PathVariable String id) { - traceService.deleteTrace(id); - ApiResponse response = new ApiResponse(true, "Trace deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/UserController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/UserController.java deleted file mode 100644 index 63baaf5..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/UserController.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.CreateUserRequest; -import com.scoop.hackathon.dto.UpdateUserRequest; -import com.scoop.hackathon.dto.UserDto; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.UserService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/users") -public class UserController { - - private final UserService userService; - - public UserController(UserService userService) { - this.userService = userService; - } - - @PostMapping - public ResponseEntity createUser(@Valid @RequestBody CreateUserRequest request) { - UserDto user = userService.createUser(request); - return new ResponseEntity<>(user, HttpStatus.CREATED); - } - - @GetMapping("/{id}") - public ResponseEntity getUserById(@PathVariable String id) { - UserDto user = userService.getUserById(id); - return ResponseEntity.ok(user); - } - - @GetMapping("/email/{email}") - public ResponseEntity getUserByEmail(@PathVariable String email) { - UserDto user = userService.getUserByEmail(email); - return ResponseEntity.ok(user); - } - - @GetMapping - public ResponseEntity> getAllUsers() { - List users = userService.getAllUsers(); - return ResponseEntity.ok(users); - } - - @PutMapping("/{id}") - public ResponseEntity updateUser( - @PathVariable String id, - @Valid @RequestBody UpdateUserRequest request) { - UserDto user = userService.updateUser(id, request); - return ResponseEntity.ok(user); - } - - @DeleteMapping("/{id}") - public ResponseEntity deleteUser(@PathVariable String id) { - userService.deleteUser(id); - ApiResponse response = new ApiResponse(true, "User deleted successfully"); - return ResponseEntity.ok(response); - } - - @GetMapping("/exists/email/{email}") - public ResponseEntity checkEmailExists(@PathVariable String email) { - boolean exists = userService.existsByEmail(email); - return ResponseEntity.ok(exists); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/VerificationTokenController.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/VerificationTokenController.java deleted file mode 100644 index c40ab14..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/controller/VerificationTokenController.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.scoop.hackathon.controller; - -import com.scoop.hackathon.dto.CreateVerificationTokenRequest; -import com.scoop.hackathon.dto.UpdateVerificationTokenRequest; -import com.scoop.hackathon.dto.VerificationTokenDto; -import com.scoop.hackathon.payload.ApiResponse; -import com.scoop.hackathon.service.VerificationTokenService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("/api/verification-tokens") -public class VerificationTokenController { - - private final VerificationTokenService verificationTokenService; - - public VerificationTokenController(VerificationTokenService verificationTokenService) { - this.verificationTokenService = verificationTokenService; - } - - @PostMapping - public ResponseEntity createVerificationToken( - @Valid @RequestBody CreateVerificationTokenRequest request) { - VerificationTokenDto token = verificationTokenService.createVerificationToken(request); - return new ResponseEntity<>(token, HttpStatus.CREATED); - } - - @GetMapping("/{identifier}") - public ResponseEntity getVerificationTokenById(@PathVariable String identifier) { - VerificationTokenDto token = verificationTokenService.getVerificationTokenById(identifier); - return ResponseEntity.ok(token); - } - - @GetMapping("/token/{token}") - public ResponseEntity getVerificationTokenByToken(@PathVariable String token) { - VerificationTokenDto verificationToken = verificationTokenService.getVerificationTokenByToken(token); - return ResponseEntity.ok(verificationToken); - } - - @GetMapping - public ResponseEntity> getAllVerificationTokens() { - List tokens = verificationTokenService.getAllVerificationTokens(); - return ResponseEntity.ok(tokens); - } - - @PutMapping("/{identifier}") - public ResponseEntity updateVerificationToken( - @PathVariable String identifier, - @Valid @RequestBody UpdateVerificationTokenRequest request) { - VerificationTokenDto token = verificationTokenService.updateVerificationToken(identifier, request); - return ResponseEntity.ok(token); - } - - @DeleteMapping("/{identifier}") - public ResponseEntity deleteVerificationToken(@PathVariable String identifier) { - verificationTokenService.deleteVerificationToken(identifier); - ApiResponse response = new ApiResponse(true, "VerificationToken deleted successfully"); - return ResponseEntity.ok(response); - } - - @DeleteMapping("/token/{token}") - public ResponseEntity deleteVerificationTokenByToken(@PathVariable String token) { - verificationTokenService.deleteVerificationTokenByToken(token); - ApiResponse response = new ApiResponse(true, "VerificationToken deleted successfully"); - return ResponseEntity.ok(response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AccountDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AccountDto.java deleted file mode 100644 index c71713a..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AccountDto.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.scoop.hackathon.dto; - -public class AccountDto { - - private String id; - private String type; - private String provider; - private String providerAccountId; - private String refreshToken; - private String accessToken; - private Integer expiresAt; - private String tokenType; - private String scope; - private String idToken; - private String sessionState; - private String userId; - - public AccountDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getProvider() { - return provider; - } - - public void setProvider(String provider) { - this.provider = provider; - } - - public String getProviderAccountId() { - return providerAccountId; - } - - public void setProviderAccountId(String providerAccountId) { - this.providerAccountId = providerAccountId; - } - - public String getRefreshToken() { - return refreshToken; - } - - public void setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public Integer getExpiresAt() { - return expiresAt; - } - - public void setExpiresAt(Integer expiresAt) { - this.expiresAt = expiresAt; - } - - public String getTokenType() { - return tokenType; - } - - public void setTokenType(String tokenType) { - this.tokenType = tokenType; - } - - public String getScope() { - return scope; - } - - public void setScope(String scope) { - this.scope = scope; - } - - public String getIdToken() { - return idToken; - } - - public void setIdToken(String idToken) { - this.idToken = idToken; - } - - public String getSessionState() { - return sessionState; - } - - public void setSessionState(String sessionState) { - this.sessionState = sessionState; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AuthResponse.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AuthResponse.java deleted file mode 100644 index 82741a7..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AuthResponse.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class AuthResponse { - - private String token; - private String type = "Bearer"; - private String userId; - private String email; - private String name; - private LocalDateTime expiresAt; - - public AuthResponse() { - } - - public AuthResponse(String token, String userId, String email, String name, LocalDateTime expiresAt) { - this.token = token; - this.userId = userId; - this.email = email; - this.name = name; - this.expiresAt = expiresAt; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public LocalDateTime getExpiresAt() { - return expiresAt; - } - - public void setExpiresAt(LocalDateTime expiresAt) { - this.expiresAt = expiresAt; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AuthenticationHistoryDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AuthenticationHistoryDto.java deleted file mode 100644 index 554be4f..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/AuthenticationHistoryDto.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class AuthenticationHistoryDto { - - private String id; - private String userId; - private String action; - private String ipAddress; - private String userAgent; - private String tokenId; - private Boolean success; - private String failureReason; - private LocalDateTime createdAt; - - public AuthenticationHistoryDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getAction() { - return action; - } - - public void setAction(String action) { - this.action = action; - } - - public String getIpAddress() { - return ipAddress; - } - - public void setIpAddress(String ipAddress) { - this.ipAddress = ipAddress; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public String getTokenId() { - return tokenId; - } - - public void setTokenId(String tokenId) { - this.tokenId = tokenId; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getFailureReason() { - return failureReason; - } - - public void setFailureReason(String failureReason) { - this.failureReason = failureReason; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateAccountRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateAccountRequest.java deleted file mode 100644 index 4b27447..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateAccountRequest.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -public class CreateAccountRequest { - - @NotBlank(message = "Type is required") - private String type; - - @NotBlank(message = "Provider is required") - private String provider; - - @NotBlank(message = "Provider Account ID is required") - private String providerAccountId; - - @NotNull(message = "User ID is required") - private String userId; - - private String refreshToken; - private String accessToken; - private Integer expiresAt; - private String tokenType; - private String scope; - private String idToken; - private String sessionState; - - public CreateAccountRequest() { - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getProvider() { - return provider; - } - - public void setProvider(String provider) { - this.provider = provider; - } - - public String getProviderAccountId() { - return providerAccountId; - } - - public void setProviderAccountId(String providerAccountId) { - this.providerAccountId = providerAccountId; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getRefreshToken() { - return refreshToken; - } - - public void setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public Integer getExpiresAt() { - return expiresAt; - } - - public void setExpiresAt(Integer expiresAt) { - this.expiresAt = expiresAt; - } - - public String getTokenType() { - return tokenType; - } - - public void setTokenType(String tokenType) { - this.tokenType = tokenType; - } - - public String getScope() { - return scope; - } - - public void setScope(String scope) { - this.scope = scope; - } - - public String getIdToken() { - return idToken; - } - - public void setIdToken(String idToken) { - this.idToken = idToken; - } - - public String getSessionState() { - return sessionState; - } - - public void setSessionState(String sessionState) { - this.sessionState = sessionState; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateAuthenticationHistoryRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateAuthenticationHistoryRequest.java deleted file mode 100644 index f4afb81..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateAuthenticationHistoryRequest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -public class CreateAuthenticationHistoryRequest { - - @NotBlank(message = "User ID is required") - private String userId; - - @NotBlank(message = "Action is required") - private String action; // LOGIN, LOGOUT, TOKEN_REFRESH, TOKEN_VALIDATED, TOKEN_EXPIRED, LOGIN_FAILED - - private String ipAddress; - - private String userAgent; - - private String tokenId; // JWT ID (jti claim) - - @NotNull(message = "Success status is required") - private Boolean success; - - private String failureReason; - - public CreateAuthenticationHistoryRequest() { - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getAction() { - return action; - } - - public void setAction(String action) { - this.action = action; - } - - public String getIpAddress() { - return ipAddress; - } - - public void setIpAddress(String ipAddress) { - this.ipAddress = ipAddress; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public String getTokenId() { - return tokenId; - } - - public void setTokenId(String tokenId) { - this.tokenId = tokenId; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getFailureReason() { - return failureReason; - } - - public void setFailureReason(String failureReason) { - this.failureReason = failureReason; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateEventRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateEventRequest.java deleted file mode 100644 index 23d0ce2..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateEventRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.NotBlank; - -public class CreateEventRequest { - - @NotBlank(message = "Type is required") - private String type; - - private String userId; - private String gitSessionId; - private String metadata; - - public CreateEventRequest() { - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getMetadata() { - return metadata; - } - - public void setMetadata(String metadata) { - this.metadata = metadata; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateGitSessionRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateGitSessionRequest.java deleted file mode 100644 index b8cb693..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateGitSessionRequest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.scoop.hackathon.dto; - -public class CreateGitSessionRequest { - private String title; - private String os; - private String repoRootHash; - private String userId; - - public CreateGitSessionRequest() { - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getOs() { - return os; - } - - public void setOs(String os) { - this.os = os; - } - - public String getRepoRootHash() { - return repoRootHash; - } - - public void setRepoRootHash(String repoRootHash) { - this.repoRootHash = repoRootHash; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreatePlanRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreatePlanRequest.java deleted file mode 100644 index 16c1e5b..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreatePlanRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -public class CreatePlanRequest { - - @NotBlank(message = "Git Session ID is required") - private String gitSessionId; - - @NotBlank(message = "Plan JSON is required") - private String planJson; - - private String issueType; - private String risk; - private Boolean dangerousAllowed; - - public CreatePlanRequest() { - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getPlanJson() { - return planJson; - } - - public void setPlanJson(String planJson) { - this.planJson = planJson; - } - - public String getIssueType() { - return issueType; - } - - public void setIssueType(String issueType) { - this.issueType = issueType; - } - - public String getRisk() { - return risk; - } - - public void setRisk(String risk) { - this.risk = risk; - } - - public Boolean getDangerousAllowed() { - return dangerousAllowed; - } - - public void setDangerousAllowed(Boolean dangerousAllowed) { - this.dangerousAllowed = dangerousAllowed; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateSessionRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateSessionRequest.java deleted file mode 100644 index e60871d..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateSessionRequest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; - -public class CreateSessionRequest { - - @NotBlank(message = "Session token is required") - private String sessionToken; - - @NotNull(message = "Expires date is required") - private LocalDateTime expires; - - @NotBlank(message = "User ID is required") - private String userId; - - public CreateSessionRequest() { - } - - public String getSessionToken() { - return sessionToken; - } - - public void setSessionToken(String sessionToken) { - this.sessionToken = sessionToken; - } - - public LocalDateTime getExpires() { - return expires; - } - - public void setExpires(LocalDateTime expires) { - this.expires = expires; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateSnapshotRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateSnapshotRequest.java deleted file mode 100644 index 2f1c45f..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateSnapshotRequest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.NotBlank; - -public class CreateSnapshotRequest { - - @NotBlank(message = "Git Session ID is required") - private String gitSessionId; - - @NotBlank(message = "Snapshot JSON is required") - private String snapshotJson; - - private Boolean truncated; - - public CreateSnapshotRequest() { - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getSnapshotJson() { - return snapshotJson; - } - - public void setSnapshotJson(String snapshotJson) { - this.snapshotJson = snapshotJson; - } - - public Boolean getTruncated() { - return truncated; - } - - public void setTruncated(Boolean truncated) { - this.truncated = truncated; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateTraceRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateTraceRequest.java deleted file mode 100644 index 9ba58eb..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateTraceRequest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.NotBlank; - -public class CreateTraceRequest { - - @NotBlank(message = "Git Session ID is required") - private String gitSessionId; - - @NotBlank(message = "Stage is required") - private String stage; - - @NotBlank(message = "Output JSON is required") - private String outputJson; - - private String snapshotId; - private Integer durationMs; - private Boolean success; - private String errorMessage; - - public CreateTraceRequest() { - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getStage() { - return stage; - } - - public void setStage(String stage) { - this.stage = stage; - } - - public String getOutputJson() { - return outputJson; - } - - public void setOutputJson(String outputJson) { - this.outputJson = outputJson; - } - - public String getSnapshotId() { - return snapshotId; - } - - public void setSnapshotId(String snapshotId) { - this.snapshotId = snapshotId; - } - - public Integer getDurationMs() { - return durationMs; - } - - public void setDurationMs(Integer durationMs) { - this.durationMs = durationMs; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateUserRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateUserRequest.java deleted file mode 100644 index ed90e02..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateUserRequest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.Email; - -public class CreateUserRequest { - private String name; - - @Email(message = "Email should be valid") - private String email; - private String password; - private String image; - - public CreateUserRequest() { - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateVerificationTokenRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateVerificationTokenRequest.java deleted file mode 100644 index 2210cab..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/CreateVerificationTokenRequest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.time.LocalDateTime; - -public class CreateVerificationTokenRequest { - - @NotBlank(message = "Identifier is required") - private String identifier; - - @NotBlank(message = "Token is required") - private String token; - - @NotNull(message = "Expires date is required") - private LocalDateTime expires; - - public CreateVerificationTokenRequest() { - } - - public String getIdentifier() { - return identifier; - } - - public void setIdentifier(String identifier) { - this.identifier = identifier; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public LocalDateTime getExpires() { - return expires; - } - - public void setExpires(LocalDateTime expires) { - this.expires = expires; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/EventDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/EventDto.java deleted file mode 100644 index 7f90029..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/EventDto.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class EventDto { - - private String id; - private LocalDateTime createdAt; - private String type; - private String userId; - private String gitSessionId; - private String metadata; - - public EventDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getMetadata() { - return metadata; - } - - public void setMetadata(String metadata) { - this.metadata = metadata; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/GitSessionDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/GitSessionDto.java deleted file mode 100644 index b64c32a..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/GitSessionDto.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class GitSessionDto { - private String id; - private String title; - private String os; - private String repoRootHash; - private String userId; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - public GitSessionDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getOs() { - return os; - } - - public void setOs(String os) { - this.os = os; - } - - public String getRepoRootHash() { - return repoRootHash; - } - - public void setRepoRootHash(String repoRootHash) { - this.repoRootHash = repoRootHash; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/LoginRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/LoginRequest.java deleted file mode 100644 index e64b79f..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/LoginRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; - -public class LoginRequest { - - @NotBlank(message = "Email is required") - @Email(message = "Email must be valid") - private String email; - - @NotBlank(message = "Password is required") - private String password; - - public LoginRequest() { - } - - public LoginRequest(String email, String password) { - this.email = email; - this.password = password; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/PlanDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/PlanDto.java deleted file mode 100644 index d7a70c0..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/PlanDto.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class PlanDto { - - private String id; - private LocalDateTime createdAt; - private String gitSessionId; - private String issueType; - private String risk; - private String planJson; - private Boolean dangerousAllowed; - - public PlanDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getIssueType() { - return issueType; - } - - public void setIssueType(String issueType) { - this.issueType = issueType; - } - - public String getRisk() { - return risk; - } - - public void setRisk(String risk) { - this.risk = risk; - } - - public String getPlanJson() { - return planJson; - } - - public void setPlanJson(String planJson) { - this.planJson = planJson; - } - - public Boolean getDangerousAllowed() { - return dangerousAllowed; - } - - public void setDangerousAllowed(Boolean dangerousAllowed) { - this.dangerousAllowed = dangerousAllowed; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/RegisterRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/RegisterRequest.java deleted file mode 100644 index 28ba8d0..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/RegisterRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; - -public class RegisterRequest { - - @NotBlank(message = "Name is required") - @Size(min = 2, max = 100, message = "Name must be between 2 and 100 characters") - private String name; - - @NotBlank(message = "Email is required") - @Email(message = "Email must be valid") - private String email; - - @NotBlank(message = "Password is required") - @Size(min = 8, message = "Password must be at least 8 characters long") - private String password; - - public RegisterRequest() { - } - - public RegisterRequest(String name, String email, String password) { - this.name = name; - this.email = email; - this.password = password; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/SessionDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/SessionDto.java deleted file mode 100644 index a51e453..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/SessionDto.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class SessionDto { - - private String id; - private String sessionToken; - private LocalDateTime expires; - private String userId; - - public SessionDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getSessionToken() { - return sessionToken; - } - - public void setSessionToken(String sessionToken) { - this.sessionToken = sessionToken; - } - - public LocalDateTime getExpires() { - return expires; - } - - public void setExpires(LocalDateTime expires) { - this.expires = expires; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/SnapshotDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/SnapshotDto.java deleted file mode 100644 index 7195b00..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/SnapshotDto.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class SnapshotDto { - - private String id; - private LocalDateTime createdAt; - private String gitSessionId; - private String snapshotJson; - private Boolean truncated; - - public SnapshotDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getSnapshotJson() { - return snapshotJson; - } - - public void setSnapshotJson(String snapshotJson) { - this.snapshotJson = snapshotJson; - } - - public Boolean getTruncated() { - return truncated; - } - - public void setTruncated(Boolean truncated) { - this.truncated = truncated; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/TraceDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/TraceDto.java deleted file mode 100644 index b4bde1d..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/TraceDto.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class TraceDto { - - private String id; - private LocalDateTime createdAt; - private String gitSessionId; - private String stage; - private String snapshotId; - private String outputJson; - private Integer durationMs; - private Boolean success; - private String errorMessage; - - public TraceDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getStage() { - return stage; - } - - public void setStage(String stage) { - this.stage = stage; - } - - public String getSnapshotId() { - return snapshotId; - } - - public void setSnapshotId(String snapshotId) { - this.snapshotId = snapshotId; - } - - public String getOutputJson() { - return outputJson; - } - - public void setOutputJson(String outputJson) { - this.outputJson = outputJson; - } - - public Integer getDurationMs() { - return durationMs; - } - - public void setDurationMs(Integer durationMs) { - this.durationMs = durationMs; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateAccountRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateAccountRequest.java deleted file mode 100644 index 6b2f367..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateAccountRequest.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.scoop.hackathon.dto; - -public class UpdateAccountRequest { - - private String type; - private String provider; - private String providerAccountId; - private String refreshToken; - private String accessToken; - private Integer expiresAt; - private String tokenType; - private String scope; - private String idToken; - private String sessionState; - private String userId; - - public UpdateAccountRequest() { - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getProvider() { - return provider; - } - - public void setProvider(String provider) { - this.provider = provider; - } - - public String getProviderAccountId() { - return providerAccountId; - } - - public void setProviderAccountId(String providerAccountId) { - this.providerAccountId = providerAccountId; - } - - public String getRefreshToken() { - return refreshToken; - } - - public void setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public Integer getExpiresAt() { - return expiresAt; - } - - public void setExpiresAt(Integer expiresAt) { - this.expiresAt = expiresAt; - } - - public String getTokenType() { - return tokenType; - } - - public void setTokenType(String tokenType) { - this.tokenType = tokenType; - } - - public String getScope() { - return scope; - } - - public void setScope(String scope) { - this.scope = scope; - } - - public String getIdToken() { - return idToken; - } - - public void setIdToken(String idToken) { - this.idToken = idToken; - } - - public String getSessionState() { - return sessionState; - } - - public void setSessionState(String sessionState) { - this.sessionState = sessionState; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateAuthenticationHistoryRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateAuthenticationHistoryRequest.java deleted file mode 100644 index 26686df..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateAuthenticationHistoryRequest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.scoop.hackathon.dto; - -public class UpdateAuthenticationHistoryRequest { - - private String action; - private String ipAddress; - private String userAgent; - private String tokenId; - private Boolean success; - private String failureReason; - - public UpdateAuthenticationHistoryRequest() { - } - - public String getAction() { - return action; - } - - public void setAction(String action) { - this.action = action; - } - - public String getIpAddress() { - return ipAddress; - } - - public void setIpAddress(String ipAddress) { - this.ipAddress = ipAddress; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public String getTokenId() { - return tokenId; - } - - public void setTokenId(String tokenId) { - this.tokenId = tokenId; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getFailureReason() { - return failureReason; - } - - public void setFailureReason(String failureReason) { - this.failureReason = failureReason; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateEventRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateEventRequest.java deleted file mode 100644 index 454e6a2..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateEventRequest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.scoop.hackathon.dto; - -public class UpdateEventRequest { - - private String type; - private String userId; - private String gitSessionId; - private String metadata; - - public UpdateEventRequest() { - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getMetadata() { - return metadata; - } - - public void setMetadata(String metadata) { - this.metadata = metadata; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdatePlanRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdatePlanRequest.java deleted file mode 100644 index fa2b718..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdatePlanRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.scoop.hackathon.dto; - -public class UpdatePlanRequest { - - private String gitSessionId; - private String issueType; - private String risk; - private String planJson; - private Boolean dangerousAllowed; - - public UpdatePlanRequest() { - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getIssueType() { - return issueType; - } - - public void setIssueType(String issueType) { - this.issueType = issueType; - } - - public String getRisk() { - return risk; - } - - public void setRisk(String risk) { - this.risk = risk; - } - - public String getPlanJson() { - return planJson; - } - - public void setPlanJson(String planJson) { - this.planJson = planJson; - } - - public Boolean getDangerousAllowed() { - return dangerousAllowed; - } - - public void setDangerousAllowed(Boolean dangerousAllowed) { - this.dangerousAllowed = dangerousAllowed; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateSessionRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateSessionRequest.java deleted file mode 100644 index d5a4654..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateSessionRequest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class UpdateSessionRequest { - - private String sessionToken; - private LocalDateTime expires; - private String userId; - - public UpdateSessionRequest() { - } - - public String getSessionToken() { - return sessionToken; - } - - public void setSessionToken(String sessionToken) { - this.sessionToken = sessionToken; - } - - public LocalDateTime getExpires() { - return expires; - } - - public void setExpires(LocalDateTime expires) { - this.expires = expires; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateSnapshotRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateSnapshotRequest.java deleted file mode 100644 index 837120a..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateSnapshotRequest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.scoop.hackathon.dto; - -public class UpdateSnapshotRequest { - - private String gitSessionId; - private String snapshotJson; - private Boolean truncated; - - public UpdateSnapshotRequest() { - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getSnapshotJson() { - return snapshotJson; - } - - public void setSnapshotJson(String snapshotJson) { - this.snapshotJson = snapshotJson; - } - - public Boolean getTruncated() { - return truncated; - } - - public void setTruncated(Boolean truncated) { - this.truncated = truncated; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateTraceRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateTraceRequest.java deleted file mode 100644 index 6231da5..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateTraceRequest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.scoop.hackathon.dto; - -public class UpdateTraceRequest { - - private String gitSessionId; - private String stage; - private String snapshotId; - private String outputJson; - private Integer durationMs; - private Boolean success; - private String errorMessage; - - public UpdateTraceRequest() { - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getStage() { - return stage; - } - - public void setStage(String stage) { - this.stage = stage; - } - - public String getSnapshotId() { - return snapshotId; - } - - public void setSnapshotId(String snapshotId) { - this.snapshotId = snapshotId; - } - - public String getOutputJson() { - return outputJson; - } - - public void setOutputJson(String outputJson) { - this.outputJson = outputJson; - } - - public Integer getDurationMs() { - return durationMs; - } - - public void setDurationMs(Integer durationMs) { - this.durationMs = durationMs; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateUserRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateUserRequest.java deleted file mode 100644 index 1f670de..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateUserRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.scoop.hackathon.dto; - -import jakarta.validation.constraints.Email; - -public class UpdateUserRequest { - private String name; - - @Email(message = "Email should be valid") - private String email; - private String image; - - public UpdateUserRequest() { - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateVerificationTokenRequest.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateVerificationTokenRequest.java deleted file mode 100644 index 31c308c..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UpdateVerificationTokenRequest.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class UpdateVerificationTokenRequest { - - private String token; - private LocalDateTime expires; - - public UpdateVerificationTokenRequest() { - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public LocalDateTime getExpires() { - return expires; - } - - public void setExpires(LocalDateTime expires) { - this.expires = expires; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UserDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UserDto.java deleted file mode 100644 index 143e29a..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/UserDto.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class UserDto { - private String id; - private String name; - private String email; - private LocalDateTime emailVerified; - private String image; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - public UserDto() { - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public LocalDateTime getEmailVerified() { - return emailVerified; - } - - public void setEmailVerified(LocalDateTime emailVerified) { - this.emailVerified = emailVerified; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/VerificationTokenDto.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/VerificationTokenDto.java deleted file mode 100644 index 7fb44d7..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/dto/VerificationTokenDto.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.scoop.hackathon.dto; - -import java.time.LocalDateTime; - -public class VerificationTokenDto { - - private String identifier; - private String token; - private LocalDateTime expires; - - public VerificationTokenDto() { - } - - public String getIdentifier() { - return identifier; - } - - public void setIdentifier(String identifier) { - this.identifier = identifier; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public LocalDateTime getExpires() { - return expires; - } - - public void setExpires(LocalDateTime expires) { - this.expires = expires; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Account.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Account.java deleted file mode 100644 index 818a9b0..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Account.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; - -@Entity -@Table(name = "account") -public class Account { - - @Id - @Column(name = "id", length = 25) - private String id; - - @Column(name = "type", nullable = false) - private String type; - - @Column(name = "provider", nullable = false) - private String provider; - - @Column(name = "provider_account_id", nullable = false) - private String providerAccountId; - - @Column(name = "refresh_token", columnDefinition = "TEXT") - private String refreshToken; - - @Column(name = "access_token", columnDefinition = "TEXT") - private String accessToken; - - @Column(name = "expires_at") - private Integer expiresAt; - - @Column(name = "token_type") - private String tokenType; - - @Column(name = "scope") - private String scope; - - @Column(name = "id_token", columnDefinition = "TEXT") - private String idToken; - - @Column(name = "session_state") - private String sessionState; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - public Account() { - } - - public Account(String id, String type, String provider, String providerAccountId, User user) { - this.id = id; - this.type = type; - this.provider = provider; - this.providerAccountId = providerAccountId; - this.user = user; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getProvider() { - return provider; - } - - public void setProvider(String provider) { - this.provider = provider; - } - - public String getProviderAccountId() { - return providerAccountId; - } - - public void setProviderAccountId(String providerAccountId) { - this.providerAccountId = providerAccountId; - } - - public String getRefreshToken() { - return refreshToken; - } - - public void setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public Integer getExpiresAt() { - return expiresAt; - } - - public void setExpiresAt(Integer expiresAt) { - this.expiresAt = expiresAt; - } - - public String getTokenType() { - return tokenType; - } - - public void setTokenType(String tokenType) { - this.tokenType = tokenType; - } - - public String getScope() { - return scope; - } - - public void setScope(String scope) { - this.scope = scope; - } - - public String getIdToken() { - return idToken; - } - - public void setIdToken(String idToken) { - this.idToken = idToken; - } - - public String getSessionState() { - return sessionState; - } - - public void setSessionState(String sessionState) { - this.sessionState = sessionState; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/AuthenticationHistory.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/AuthenticationHistory.java deleted file mode 100644 index 2f280cf..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/AuthenticationHistory.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.UpdateTimestamp; -import java.time.LocalDateTime; - -@Entity -@Table(name = "authentication_history") -public class AuthenticationHistory { - - @Id - @Column(name = "id", length = 25) - private String id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @Column(name = "action", nullable = false, length = 50) - private String action; // LOGIN, LOGOUT, TOKEN_REFRESH, TOKEN_VALIDATED, TOKEN_EXPIRED, LOGIN_FAILED - - @Column(name = "ip_address", length = 45) - private String ipAddress; - - @Column(name = "user_agent", length = 500) - private String userAgent; - - @Column(name = "token_id", length = 255) - private String tokenId; // JWT ID (jti claim) - - @Column(name = "success", nullable = false) - private Boolean success; - - @Column(name = "failure_reason", length = 500) - private String failureReason; - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @Column(name = "updated_at", nullable = false) - @UpdateTimestamp - private LocalDateTime updatedAt; - - @PrePersist - protected void onCreate() { - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } - if (updatedAt == null) { - updatedAt = LocalDateTime.now(); - } - } - - public AuthenticationHistory() { - } - - public AuthenticationHistory(String id, User user, String action, String ipAddress, String userAgent, Boolean success) { - this.id = id; - this.user = user; - this.action = action; - this.ipAddress = ipAddress; - this.userAgent = userAgent; - this.success = success; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - - public String getAction() { - return action; - } - - public void setAction(String action) { - this.action = action; - } - - public String getIpAddress() { - return ipAddress; - } - - public void setIpAddress(String ipAddress) { - this.ipAddress = ipAddress; - } - - public String getUserAgent() { - return userAgent; - } - - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - - public String getTokenId() { - return tokenId; - } - - public void setTokenId(String tokenId) { - this.tokenId = tokenId; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getFailureReason() { - return failureReason; - } - - public void setFailureReason(String failureReason) { - this.failureReason = failureReason; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Event.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Event.java deleted file mode 100644 index 04a119a..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Event.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; -import java.time.LocalDateTime; - -@Entity -@Table(name = "event") -public class Event { - - @Id - @Column(name = "id", length = 25) - private String id; - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @Column(name = "type", nullable = false) - private String type; - - @Column(name = "user_id", length = 25) - private String userId; - - @Column(name = "git_session_id", length = 25) - private String gitSessionId; - - @JdbcTypeCode(SqlTypes.JSON) - @Column(name = "metadata", columnDefinition = "json") - private String metadata; - - @PrePersist - protected void onCreate() { - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } - } - - public Event() { - } - - public Event(String id, String type) { - this.id = id; - this.type = type; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getGitSessionId() { - return gitSessionId; - } - - public void setGitSessionId(String gitSessionId) { - this.gitSessionId = gitSessionId; - } - - public String getMetadata() { - return metadata; - } - - public void setMetadata(String metadata) { - this.metadata = metadata; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/GitSession.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/GitSession.java deleted file mode 100644 index 947329c..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/GitSession.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.UpdateTimestamp; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Entity -@Table(name = "git_session") -public class GitSession { - - @Id - @Column(name = "id", length = 25) - private String id; - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @Column(name = "updated_at", nullable = false) - @UpdateTimestamp - private LocalDateTime updatedAt; - - @Column(name = "title") - private String title; - - @Column(name = "os") - private String os; - - @Column(name = "repo_root_hash") - private String repoRootHash; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; - - @OneToMany(mappedBy = "gitSession", cascade = CascadeType.ALL, orphanRemoval = true) - private List snapshots = new ArrayList<>(); - - @OneToMany(mappedBy = "gitSession", cascade = CascadeType.ALL, orphanRemoval = true) - private List plans = new ArrayList<>(); - - @OneToMany(mappedBy = "gitSession", cascade = CascadeType.ALL, orphanRemoval = true) - private List traces = new ArrayList<>(); - - @PrePersist - protected void onCreate() { - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } - if (updatedAt == null) { - updatedAt = LocalDateTime.now(); - } - } - - public GitSession() { - } - - public GitSession(String id) { - this.id = id; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getOs() { - return os; - } - - public void setOs(String os) { - this.os = os; - } - - public String getRepoRootHash() { - return repoRootHash; - } - - public void setRepoRootHash(String repoRootHash) { - this.repoRootHash = repoRootHash; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } - - public List getSnapshots() { - return snapshots; - } - - public void setSnapshots(List snapshots) { - this.snapshots = snapshots; - } - - public List getPlans() { - return plans; - } - - public void setPlans(List plans) { - this.plans = plans; - } - - public List getTraces() { - return traces; - } - - public void setTraces(List traces) { - this.traces = traces; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Plan.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Plan.java deleted file mode 100644 index c656982..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Plan.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; -import java.time.LocalDateTime; - -@Entity -@Table(name = "plan") -public class Plan { - - @Id - @Column(name = "id", length = 25) - private String id; - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "git_session_id", nullable = false) - private GitSession gitSession; - - @Column(name = "issue_type") - private String issueType; - - @Column(name = "risk") - private String risk; - - @JdbcTypeCode(SqlTypes.JSON) - @Column(name = "plan_json", nullable = false, columnDefinition = "json") - private String planJson; - - @Column(name = "dangerous_allowed", nullable = false) - private Boolean dangerousAllowed = false; - - @PrePersist - protected void onCreate() { - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } - if (dangerousAllowed == null) { - dangerousAllowed = false; - } - } - - public Plan() { - } - - public Plan(String id, GitSession gitSession, String planJson) { - this.id = id; - this.gitSession = gitSession; - this.planJson = planJson; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public GitSession getGitSession() { - return gitSession; - } - - public void setGitSession(GitSession gitSession) { - this.gitSession = gitSession; - } - - public String getIssueType() { - return issueType; - } - - public void setIssueType(String issueType) { - this.issueType = issueType; - } - - public String getRisk() { - return risk; - } - - public void setRisk(String risk) { - this.risk = risk; - } - - public String getPlanJson() { - return planJson; - } - - public void setPlanJson(String planJson) { - this.planJson = planJson; - } - - public Boolean getDangerousAllowed() { - return dangerousAllowed; - } - - public void setDangerousAllowed(Boolean dangerousAllowed) { - this.dangerousAllowed = dangerousAllowed; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Session.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Session.java deleted file mode 100644 index d835736..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Session.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import java.time.LocalDateTime; - -@Entity -@Table(name = "session") -public class Session { - - @Id - @Column(name = "id", length = 25) - private String id; - - @Column(name = "session_token", nullable = false, unique = true) - private String sessionToken; - - @Column(name = "expires", nullable = false) - private LocalDateTime expires; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; - - public Session() { - } - - public Session(String id, String sessionToken, LocalDateTime expires, User user) { - this.id = id; - this.sessionToken = sessionToken; - this.expires = expires; - this.user = user; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getSessionToken() { - return sessionToken; - } - - public void setSessionToken(String sessionToken) { - this.sessionToken = sessionToken; - } - - public LocalDateTime getExpires() { - return expires; - } - - public void setExpires(LocalDateTime expires) { - this.expires = expires; - } - - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Snapshot.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Snapshot.java deleted file mode 100644 index f89b29d..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Snapshot.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Entity -@Table(name = "snapshot") -public class Snapshot { - - @Id - @Column(name = "id", length = 25) - private String id; - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "git_session_id", nullable = false) - private GitSession gitSession; - - @JdbcTypeCode(SqlTypes.JSON) - @Column(name = "snapshot_json", nullable = false, columnDefinition = "json") - private String snapshotJson; - - @Column(name = "truncated", nullable = false) - private Boolean truncated = false; - - @OneToMany(mappedBy = "snapshot", cascade = CascadeType.ALL, orphanRemoval = true) - private List traces = new ArrayList<>(); - - @PrePersist - protected void onCreate() { - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } - if (truncated == null) { - truncated = false; - } - } - - public Snapshot() { - } - - public Snapshot(String id, GitSession gitSession, String snapshotJson) { - this.id = id; - this.gitSession = gitSession; - this.snapshotJson = snapshotJson; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public GitSession getGitSession() { - return gitSession; - } - - public void setGitSession(GitSession gitSession) { - this.gitSession = gitSession; - } - - public String getSnapshotJson() { - return snapshotJson; - } - - public void setSnapshotJson(String snapshotJson) { - this.snapshotJson = snapshotJson; - } - - public Boolean getTruncated() { - return truncated; - } - - public void setTruncated(Boolean truncated) { - this.truncated = truncated; - } - - public List getTraces() { - return traces; - } - - public void setTraces(List traces) { - this.traces = traces; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Trace.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Trace.java deleted file mode 100644 index e7dc0dd..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/Trace.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.JdbcTypeCode; -import org.hibernate.type.SqlTypes; -import java.time.LocalDateTime; - -@Entity -@Table(name = "trace") -public class Trace { - - @Id - @Column(name = "id", length = 25) - private String id; - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "git_session_id", nullable = false) - private GitSession gitSession; - - @Column(name = "stage", nullable = false) - private String stage; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "snapshot_id") - private Snapshot snapshot; - - @JdbcTypeCode(SqlTypes.JSON) - @Column(name = "output_json", nullable = false, columnDefinition = "json") - private String outputJson; - - @Column(name = "duration_ms") - private Integer durationMs; - - @Column(name = "success", nullable = false) - private Boolean success = true; - - @Column(name = "error_message") - private String errorMessage; - - @PrePersist - protected void onCreate() { - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } - if (success == null) { - success = true; - } - } - - public Trace() { - } - - public Trace(String id, GitSession gitSession, String stage, String outputJson) { - this.id = id; - this.gitSession = gitSession; - this.stage = stage; - this.outputJson = outputJson; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public GitSession getGitSession() { - return gitSession; - } - - public void setGitSession(GitSession gitSession) { - this.gitSession = gitSession; - } - - public String getStage() { - return stage; - } - - public void setStage(String stage) { - this.stage = stage; - } - - public Snapshot getSnapshot() { - return snapshot; - } - - public void setSnapshot(Snapshot snapshot) { - this.snapshot = snapshot; - } - - public String getOutputJson() { - return outputJson; - } - - public void setOutputJson(String outputJson) { - this.outputJson = outputJson; - } - - public Integer getDurationMs() { - return durationMs; - } - - public void setDurationMs(Integer durationMs) { - this.durationMs = durationMs; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/User.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/User.java deleted file mode 100644 index 9cbbf7a..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/User.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.UpdateTimestamp; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Entity -@Table(name = "user") -public class User { - - @Id - @Column(name = "id", length = 25) - private String id; - - @Column(name = "name") - private String name; - - @Column(name = "email") - private String email; - - @Column(name = "email_verified") - private LocalDateTime emailVerified; - - @Column(name = "image") - private String image; - - @Column(name = "password") - private String password; - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List accounts = new ArrayList<>(); - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List sessions = new ArrayList<>(); - - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private List gitSessions = new ArrayList<>(); - - @Column(name = "created_at", nullable = false, updatable = false) - private LocalDateTime createdAt; - - @Column(name = "updated_at", nullable = false) - @UpdateTimestamp - private LocalDateTime updatedAt; - - @PrePersist - protected void onCreate() { - if (createdAt == null) { - createdAt = LocalDateTime.now(); - } - if (updatedAt == null) { - updatedAt = LocalDateTime.now(); - } - } - - public User() { - } - - public User(String id) { - this.id = id; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public LocalDateTime getEmailVerified() { - return emailVerified; - } - - public void setEmailVerified(LocalDateTime emailVerified) { - this.emailVerified = emailVerified; - } - - public String getImage() { - return image; - } - - public void setImage(String image) { - this.image = image; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public List getAccounts() { - return accounts; - } - - public void setAccounts(List accounts) { - this.accounts = accounts; - } - - public List getSessions() { - return sessions; - } - - public void setSessions(List sessions) { - this.sessions = sessions; - } - - public List getGitSessions() { - return gitSessions; - } - - public void setGitSessions(List gitSessions) { - this.gitSessions = gitSessions; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/VerificationToken.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/VerificationToken.java deleted file mode 100644 index d9b3d8d..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/entity/VerificationToken.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.scoop.hackathon.entity; - -import jakarta.persistence.*; -import java.time.LocalDateTime; - -@Entity -@Table(name = "verification_token") -public class VerificationToken { - - @Id - @Column(name = "identifier") - private String identifier; - - @Column(name = "token", nullable = false) - private String token; - - @Column(name = "expires", nullable = false) - private LocalDateTime expires; - - public VerificationToken() { - } - - public VerificationToken(String identifier, String token, LocalDateTime expires) { - this.identifier = identifier; - this.token = token; - this.expires = expires; - } - - public String getIdentifier() { - return identifier; - } - - public void setIdentifier(String identifier) { - this.identifier = identifier; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public LocalDateTime getExpires() { - return expires; - } - - public void setExpires(LocalDateTime expires) { - this.expires = expires; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/BadCredentialsException.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/BadCredentialsException.java deleted file mode 100644 index d815047..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/BadCredentialsException.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.scoop.hackathon.exception; - -public class BadCredentialsException extends RuntimeException { - public BadCredentialsException(String message) { - super(message); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/GlobalExceptionHandler.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/GlobalExceptionHandler.java deleted file mode 100644 index cb1616a..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.scoop.hackathon.exception; - -import com.scoop.hackathon.payload.ApiResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.dao.DataAccessException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.transaction.TransactionException; -import org.springframework.validation.FieldError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import java.sql.SQLException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; - -@RestControllerAdvice -public class GlobalExceptionHandler { - - private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); - - @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException ex) { - ApiResponse response = new ApiResponse(false, ex.getMessage()); - return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(BadCredentialsException.class) - public ResponseEntity handleBadCredentialsException(BadCredentialsException ex) { - ApiResponse response = new ApiResponse(false, ex.getMessage()); - return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED); - } - - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex) { - ApiResponse response = new ApiResponse(false, ex.getMessage()); - return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex) { - Map errors = new HashMap<>(); - ex.getBindingResult().getAllErrors().forEach((error) -> { - String fieldName = ((FieldError) error).getField(); - String errorMessage = error.getDefaultMessage(); - errors.put(fieldName, errorMessage); - }); - - Map response = new HashMap<>(); - response.put("success", false); - response.put("message", "Validation failed"); - response.put("errors", errors); - - return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler({DataAccessException.class, TransactionException.class}) - public ResponseEntity> handleDataAccessException(Exception ex) { - logger.error("Database error occurred: ", ex); - - String errorMessage = "Database connection error"; - if (ex.getMessage() != null) { - if (ex.getMessage().contains("Unable to commit")) { - errorMessage = "Database transaction failed. Please check your database connection and ensure MySQL is running."; - } else if (ex.getMessage().contains("Connection refused") || ex.getMessage().contains("Communications link failure")) { - errorMessage = "Cannot connect to database. Please ensure MySQL is running and accessible."; - } else if (ex.getMessage().contains("Access denied")) { - errorMessage = "Database authentication failed. Please check your database credentials."; - } else if (ex.getMessage().contains("Unknown database")) { - errorMessage = "Database does not exist. Please create the database or check your configuration."; - } else { - errorMessage = "Database error: " + ex.getMessage(); - } - } - - Map response = new HashMap<>(); - response.put("success", false); - response.put("message", errorMessage); - response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - - return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler(SQLException.class) - public ResponseEntity> handleSQLException(SQLException ex) { - logger.error("SQL error occurred: ", ex); - - Map response = new HashMap<>(); - response.put("success", false); - response.put("message", "Database error: " + ex.getMessage()); - response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - - return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity> handleGlobalException(Exception ex) { - logger.error("Unexpected error occurred: ", ex); - - Map response = new HashMap<>(); - response.put("success", false); - response.put("message", "An error occurred: " + ex.getMessage()); - response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - - return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/ResourceNotFoundException.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/ResourceNotFoundException.java deleted file mode 100644 index 2d4829e..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/exception/ResourceNotFoundException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.scoop.hackathon.exception; - -public class ResourceNotFoundException extends RuntimeException { - public ResourceNotFoundException(String message) { - super(message); - } - - public ResourceNotFoundException(String resourceName, String fieldName, Object fieldValue) { - super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/payload/ApiResponse.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/payload/ApiResponse.java deleted file mode 100644 index 0e1d0bf..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/payload/ApiResponse.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.scoop.hackathon.payload; - -import java.time.LocalDateTime; - -public class ApiResponse { - private boolean success; - private String message; - private LocalDateTime timestamp; - - public ApiResponse(boolean success, String message) { - this.success = success; - this.message = message; - this.timestamp = LocalDateTime.now(); - } - - public boolean isSuccess() { - return success; - } - - public void setSuccess(boolean success) { - this.success = success; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public LocalDateTime getTimestamp() { - return timestamp; - } - - public void setTimestamp(LocalDateTime timestamp) { - this.timestamp = timestamp; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/AccountRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/AccountRepository.java deleted file mode 100644 index 6d1b6b2..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/AccountRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.Account; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -@Repository -public interface AccountRepository extends JpaRepository { - List findByUserId(String userId); - Optional findByProviderAndProviderAccountId(String provider, String providerAccountId); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/AuthenticationHistoryRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/AuthenticationHistoryRepository.java deleted file mode 100644 index 942aaf7..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/AuthenticationHistoryRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.AuthenticationHistory; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface AuthenticationHistoryRepository extends JpaRepository { - List findByUserIdOrderByCreatedAtDesc(String userId); - List findByUserIdAndActionOrderByCreatedAtDesc(String userId, String action); - List findByUserIdAndSuccessOrderByCreatedAtDesc(String userId, Boolean success); - List findByTokenId(String tokenId); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/EventRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/EventRepository.java deleted file mode 100644 index 4772b73..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/EventRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.Event; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface EventRepository extends JpaRepository { - List findByUserId(String userId); - List findByGitSessionId(String gitSessionId); - List findByType(String type); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/GitSessionRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/GitSessionRepository.java deleted file mode 100644 index 6afad5b..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/GitSessionRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.GitSession; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface GitSessionRepository extends JpaRepository { - List findByUserId(String userId); - List findByUserIdOrderByCreatedAtDesc(String userId); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/PlanRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/PlanRepository.java deleted file mode 100644 index cb4fdf6..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/PlanRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.Plan; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface PlanRepository extends JpaRepository { - List findByGitSessionId(String gitSessionId); - List findByGitSessionIdOrderByCreatedAtDesc(String gitSessionId); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/SessionRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/SessionRepository.java deleted file mode 100644 index a6d4f30..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/SessionRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.Session; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -@Repository -public interface SessionRepository extends JpaRepository { - Optional findBySessionToken(String sessionToken); - List findByUserId(String userId); - void deleteBySessionToken(String sessionToken); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/SnapshotRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/SnapshotRepository.java deleted file mode 100644 index a880d1e..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/SnapshotRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.Snapshot; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface SnapshotRepository extends JpaRepository { - List findByGitSessionId(String gitSessionId); - List findByGitSessionIdOrderByCreatedAtDesc(String gitSessionId); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/TraceRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/TraceRepository.java deleted file mode 100644 index f6a15c1..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/TraceRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.Trace; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface TraceRepository extends JpaRepository { - List findByGitSessionId(String gitSessionId); - List findByGitSessionIdOrderByCreatedAtDesc(String gitSessionId); - List findBySnapshotId(String snapshotId); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/UserRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/UserRepository.java deleted file mode 100644 index c1ba3f2..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/UserRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); - boolean existsByEmail(String email); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/VerificationTokenRepository.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/VerificationTokenRepository.java deleted file mode 100644 index b6503ca..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/repository/VerificationTokenRepository.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.scoop.hackathon.repository; - -import com.scoop.hackathon.entity.VerificationToken; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -import java.util.Optional; - -@Repository -public interface VerificationTokenRepository extends JpaRepository { - Optional findByToken(String token); - void deleteByToken(String token); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/JwtAuthenticationFilter.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/JwtAuthenticationFilter.java deleted file mode 100644 index e08e2a3..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/JwtAuthenticationFilter.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.scoop.hackathon.security; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; - -@Component -public class JwtAuthenticationFilter extends OncePerRequestFilter { - - private final JwtUtil jwtUtil; - private final UserDetailsService userDetailsService; - - public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { - this.jwtUtil = jwtUtil; - this.userDetailsService = userDetailsService; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - String requestPath = request.getRequestURI(); - - // Skip JWT authentication for Swagger/OpenAPI endpoints - if (requestPath.startsWith("/swagger-ui") || - requestPath.startsWith("/v3/api-docs") || - requestPath.startsWith("/swagger-resources") || - requestPath.startsWith("/webjars") || - requestPath.equals("/swagger-ui.html")) { - filterChain.doFilter(request, response); - return; - } - - final String authHeader = request.getHeader("Authorization"); - - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - filterChain.doFilter(request, response); - return; - } - - try { - final String jwt = authHeader.substring(7); - - if (jwtUtil.validateToken(jwt)) { - final String userEmail = jwtUtil.extractEmail(jwt); - - if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail); - - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - userDetails, - null, - userDetails.getAuthorities() - ); - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authToken); - } - } - } catch (Exception e) { - logger.error("Cannot set user authentication: {}", e); - } - - filterChain.doFilter(request, response); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/JwtUtil.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/JwtUtil.java deleted file mode 100644 index ad0f477..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/JwtUtil.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.scoop.hackathon.security; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -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.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.UUID; -import java.util.function.Function; - -@Component -public class JwtUtil { - - @Value("${jwt.secret:defaultSecretKeyThatShouldBeChangedInProductionEnvironmentMinimum32Characters}") - private String secret; - - @Value("${jwt.expiration:86400000}") // 24 hours in milliseconds - private Long expiration; - - private SecretKey getSigningKey() { - byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8); - return Keys.hmacShaKeyFor(keyBytes); - } - - public String generateToken(String userId, String email) { - Instant now = Instant.now(); - Instant expirationTime = now.plus(expiration, ChronoUnit.MILLIS); - String tokenId = UUID.randomUUID().toString(); - - // Generate a numeric ID from the userId (CUID) for frontend compatibility - // This creates a consistent numeric ID from the string CUID - long numericId = userId.hashCode() & 0x7FFFFFFF; // Convert to positive long - - return Jwts.builder() - .id(tokenId) - .subject(userId) - .claim("email", email) - .claim("id", numericId) // Add numeric id claim for frontend compatibility - .issuedAt(Date.from(now)) - .expiration(Date.from(expirationTime)) - .signWith(getSigningKey()) - .compact(); - } - - public String extractUserId(String token) { - return extractClaim(token, Claims::getSubject); - } - - public String extractEmail(String token) { - return extractClaim(token, claims -> claims.get("email", String.class)); - } - - public String extractTokenId(String token) { - return extractClaim(token, Claims::getId); - } - - public Date extractExpiration(String token) { - return extractClaim(token, Claims::getExpiration); - } - - public T extractClaim(String token, Function claimsResolver) { - final Claims claims = extractAllClaims(token); - return claimsResolver.apply(claims); - } - - private Claims extractAllClaims(String token) { - return Jwts.parser() - .verifyWith(getSigningKey()) - .build() - .parseSignedClaims(token) - .getPayload(); - } - - public Boolean isTokenExpired(String token) { - try { - Date expiration = extractExpiration(token); - return expiration.before(new Date()); - } catch (Exception e) { - return true; - } - } - - public Boolean validateToken(String token, String userId) { - try { - final String extractedUserId = extractUserId(token); - return (extractedUserId.equals(userId) && !isTokenExpired(token)); - } catch (Exception e) { - return false; - } - } - - public Boolean validateToken(String token) { - try { - // This will throw an exception if the token is invalid or signature doesn't match - extractAllClaims(token); - return !isTokenExpired(token); - } catch (Exception e) { - return false; - } - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/UserDetailsServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/UserDetailsServiceImpl.java deleted file mode 100644 index 8e01a11..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/security/UserDetailsServiceImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.scoop.hackathon.security; - -import com.scoop.hackathon.entity.User; -import com.scoop.hackathon.repository.UserRepository; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; - -@Service -public class UserDetailsServiceImpl implements UserDetailsService { - - private final UserRepository userRepository; - - public UserDetailsServiceImpl(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - User user = userRepository.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email)); - - return org.springframework.security.core.userdetails.User.builder() - .username(user.getEmail()) - .password(user.getPassword() != null ? user.getPassword() : "") - .authorities(new ArrayList<>()) - .build(); - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AccountService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AccountService.java deleted file mode 100644 index caf366f..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AccountService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.AccountDto; -import com.scoop.hackathon.dto.CreateAccountRequest; -import com.scoop.hackathon.dto.UpdateAccountRequest; - -import java.util.List; - -public interface AccountService { - AccountDto createAccount(CreateAccountRequest request); - AccountDto getAccountById(String id); - List getAllAccounts(); - List getAccountsByUserId(String userId); - AccountDto getAccountByProviderAndProviderAccountId(String provider, String providerAccountId); - AccountDto updateAccount(String id, UpdateAccountRequest request); - void deleteAccount(String id); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AccountServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AccountServiceImpl.java deleted file mode 100644 index 451a3c7..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AccountServiceImpl.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.AccountDto; -import com.scoop.hackathon.dto.CreateAccountRequest; -import com.scoop.hackathon.dto.UpdateAccountRequest; -import com.scoop.hackathon.entity.Account; -import com.scoop.hackathon.entity.User; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.AccountRepository; -import com.scoop.hackathon.repository.UserRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class AccountServiceImpl implements AccountService { - - private final AccountRepository accountRepository; - private final UserRepository userRepository; - - public AccountServiceImpl(AccountRepository accountRepository, UserRepository userRepository) { - this.accountRepository = accountRepository; - this.userRepository = userRepository; - } - - @Override - public AccountDto createAccount(CreateAccountRequest request) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", request.getUserId())); - - Account account = new Account(); - account.setId(CuidGenerator.generate()); - account.setType(request.getType()); - account.setProvider(request.getProvider()); - account.setProviderAccountId(request.getProviderAccountId()); - account.setUser(user); - account.setRefreshToken(request.getRefreshToken()); - account.setAccessToken(request.getAccessToken()); - account.setExpiresAt(request.getExpiresAt()); - account.setTokenType(request.getTokenType()); - account.setScope(request.getScope()); - account.setIdToken(request.getIdToken()); - account.setSessionState(request.getSessionState()); - - Account savedAccount = accountRepository.save(account); - return convertToDto(savedAccount); - } - - @Override - @Transactional(readOnly = true) - public AccountDto getAccountById(String id) { - Account account = accountRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Account", "id", id)); - return convertToDto(account); - } - - @Override - @Transactional(readOnly = true) - public List getAllAccounts() { - return accountRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getAccountsByUserId(String userId) { - return accountRepository.findByUserId(userId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public AccountDto getAccountByProviderAndProviderAccountId(String provider, String providerAccountId) { - Account account = accountRepository.findByProviderAndProviderAccountId(provider, providerAccountId) - .orElseThrow(() -> new ResourceNotFoundException("Account", "provider and providerAccountId", - provider + "/" + providerAccountId)); - return convertToDto(account); - } - - @Override - public AccountDto updateAccount(String id, UpdateAccountRequest request) { - Account account = accountRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Account", "id", id)); - - if (request.getType() != null) account.setType(request.getType()); - if (request.getProvider() != null) account.setProvider(request.getProvider()); - if (request.getProviderAccountId() != null) account.setProviderAccountId(request.getProviderAccountId()); - if (request.getRefreshToken() != null) account.setRefreshToken(request.getRefreshToken()); - if (request.getAccessToken() != null) account.setAccessToken(request.getAccessToken()); - if (request.getExpiresAt() != null) account.setExpiresAt(request.getExpiresAt()); - if (request.getTokenType() != null) account.setTokenType(request.getTokenType()); - if (request.getScope() != null) account.setScope(request.getScope()); - if (request.getIdToken() != null) account.setIdToken(request.getIdToken()); - if (request.getSessionState() != null) account.setSessionState(request.getSessionState()); - if (request.getUserId() != null) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", request.getUserId())); - account.setUser(user); - } - - Account updatedAccount = accountRepository.save(account); - return convertToDto(updatedAccount); - } - - @Override - public void deleteAccount(String id) { - Account account = accountRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Account", "id", id)); - accountRepository.delete(account); - } - - private AccountDto convertToDto(Account account) { - AccountDto dto = new AccountDto(); - dto.setId(account.getId()); - dto.setType(account.getType()); - dto.setProvider(account.getProvider()); - dto.setProviderAccountId(account.getProviderAccountId()); - dto.setRefreshToken(account.getRefreshToken()); - dto.setAccessToken(account.getAccessToken()); - dto.setExpiresAt(account.getExpiresAt()); - dto.setTokenType(account.getTokenType()); - dto.setScope(account.getScope()); - dto.setIdToken(account.getIdToken()); - dto.setSessionState(account.getSessionState()); - dto.setUserId(account.getUser() != null ? account.getUser().getId() : null); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationHistoryService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationHistoryService.java deleted file mode 100644 index 62beddb..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationHistoryService.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.AuthenticationHistoryDto; -import com.scoop.hackathon.dto.CreateAuthenticationHistoryRequest; -import com.scoop.hackathon.dto.UpdateAuthenticationHistoryRequest; - -import java.util.List; - -public interface AuthenticationHistoryService { - AuthenticationHistoryDto createAuthenticationHistory(CreateAuthenticationHistoryRequest request); - AuthenticationHistoryDto getAuthenticationHistoryById(String id); - List getAllAuthenticationHistory(); - List getAuthenticationHistoryByUserId(String userId); - List getAuthenticationHistoryByUserIdAndAction(String userId, String action); - List getAuthenticationHistoryByUserIdAndSuccess(String userId, Boolean success); - List getAuthenticationHistoryByTokenId(String tokenId); - AuthenticationHistoryDto updateAuthenticationHistory(String id, UpdateAuthenticationHistoryRequest request); - void deleteAuthenticationHistory(String id); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationHistoryServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationHistoryServiceImpl.java deleted file mode 100644 index 80ba7ce..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationHistoryServiceImpl.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.AuthenticationHistoryDto; -import com.scoop.hackathon.dto.CreateAuthenticationHistoryRequest; -import com.scoop.hackathon.dto.UpdateAuthenticationHistoryRequest; -import com.scoop.hackathon.entity.AuthenticationHistory; -import com.scoop.hackathon.entity.User; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.AuthenticationHistoryRepository; -import com.scoop.hackathon.repository.UserRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class AuthenticationHistoryServiceImpl implements AuthenticationHistoryService { - - private final AuthenticationHistoryRepository authenticationHistoryRepository; - private final UserRepository userRepository; - - public AuthenticationHistoryServiceImpl( - AuthenticationHistoryRepository authenticationHistoryRepository, - UserRepository userRepository) { - this.authenticationHistoryRepository = authenticationHistoryRepository; - this.userRepository = userRepository; - } - - @Override - public AuthenticationHistoryDto createAuthenticationHistory(CreateAuthenticationHistoryRequest request) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", request.getUserId())); - - AuthenticationHistory history = new AuthenticationHistory(); - history.setId(CuidGenerator.generate()); - history.setUser(user); - history.setAction(request.getAction()); - history.setIpAddress(request.getIpAddress()); - history.setUserAgent(request.getUserAgent()); - history.setTokenId(request.getTokenId()); - history.setSuccess(request.getSuccess()); - history.setFailureReason(request.getFailureReason()); - - AuthenticationHistory savedHistory = authenticationHistoryRepository.save(history); - return convertToDto(savedHistory); - } - - @Override - @Transactional(readOnly = true) - public AuthenticationHistoryDto getAuthenticationHistoryById(String id) { - AuthenticationHistory history = authenticationHistoryRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("AuthenticationHistory", "id", id)); - return convertToDto(history); - } - - @Override - @Transactional(readOnly = true) - public List getAllAuthenticationHistory() { - return authenticationHistoryRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getAuthenticationHistoryByUserId(String userId) { - return authenticationHistoryRepository.findByUserIdOrderByCreatedAtDesc(userId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getAuthenticationHistoryByUserIdAndAction(String userId, String action) { - return authenticationHistoryRepository.findByUserIdAndActionOrderByCreatedAtDesc(userId, action).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getAuthenticationHistoryByUserIdAndSuccess(String userId, Boolean success) { - return authenticationHistoryRepository.findByUserIdAndSuccessOrderByCreatedAtDesc(userId, success).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getAuthenticationHistoryByTokenId(String tokenId) { - return authenticationHistoryRepository.findByTokenId(tokenId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public AuthenticationHistoryDto updateAuthenticationHistory(String id, UpdateAuthenticationHistoryRequest request) { - AuthenticationHistory history = authenticationHistoryRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("AuthenticationHistory", "id", id)); - - if (request.getAction() != null) history.setAction(request.getAction()); - if (request.getIpAddress() != null) history.setIpAddress(request.getIpAddress()); - if (request.getUserAgent() != null) history.setUserAgent(request.getUserAgent()); - if (request.getTokenId() != null) history.setTokenId(request.getTokenId()); - if (request.getSuccess() != null) history.setSuccess(request.getSuccess()); - if (request.getFailureReason() != null) history.setFailureReason(request.getFailureReason()); - - AuthenticationHistory updatedHistory = authenticationHistoryRepository.save(history); - return convertToDto(updatedHistory); - } - - @Override - public void deleteAuthenticationHistory(String id) { - AuthenticationHistory history = authenticationHistoryRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("AuthenticationHistory", "id", id)); - authenticationHistoryRepository.delete(history); - } - - private AuthenticationHistoryDto convertToDto(AuthenticationHistory history) { - AuthenticationHistoryDto dto = new AuthenticationHistoryDto(); - dto.setId(history.getId()); - dto.setUserId(history.getUser() != null ? history.getUser().getId() : null); - dto.setAction(history.getAction()); - dto.setIpAddress(history.getIpAddress()); - dto.setUserAgent(history.getUserAgent()); - dto.setTokenId(history.getTokenId()); - dto.setSuccess(history.getSuccess()); - dto.setFailureReason(history.getFailureReason()); - dto.setCreatedAt(history.getCreatedAt()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationService.java deleted file mode 100644 index ee7ca00..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/AuthenticationService.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.AuthResponse; -import com.scoop.hackathon.dto.AuthenticationHistoryDto; -import com.scoop.hackathon.dto.LoginRequest; -import com.scoop.hackathon.dto.RegisterRequest; -import com.scoop.hackathon.entity.AuthenticationHistory; -import com.scoop.hackathon.entity.User; -import com.scoop.hackathon.exception.BadCredentialsException; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.AuthenticationHistoryRepository; -import com.scoop.hackathon.repository.UserRepository; -import com.scoop.hackathon.security.JwtUtil; -import com.scoop.hackathon.util.CuidGenerator; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class AuthenticationService { - - private final UserRepository userRepository; - private final AuthenticationHistoryRepository authHistoryRepository; - private final PasswordEncoder passwordEncoder; - private final JwtUtil jwtUtil; - - @PersistenceContext - private EntityManager entityManager; - - public AuthenticationService( - UserRepository userRepository, - AuthenticationHistoryRepository authHistoryRepository, - PasswordEncoder passwordEncoder, - JwtUtil jwtUtil) { - this.userRepository = userRepository; - this.authHistoryRepository = authHistoryRepository; - this.passwordEncoder = passwordEncoder; - this.jwtUtil = jwtUtil; - } - - public AuthResponse register(RegisterRequest request, HttpServletRequest httpRequest) { - if (userRepository.existsByEmail(request.getEmail())) { - recordAuthHistory(null, "REGISTER", getClientIp(httpRequest), getUserAgent(httpRequest), false, "Email already exists"); - throw new IllegalArgumentException("User with email " + request.getEmail() + " already exists"); - } - - User user = new User(); - user.setId(CuidGenerator.generate()); - user.setName(request.getName()); - user.setEmail(request.getEmail()); - user.setPassword(passwordEncoder.encode(request.getPassword())); - - User savedUser = userRepository.save(user); - entityManager.flush(); // Explicitly flush to ensure user is persisted - - String token = jwtUtil.generateToken(savedUser.getId(), savedUser.getEmail()); - LocalDateTime expiresAt = LocalDateTime.now().plusHours(24); - - AuthenticationHistory history = recordAuthHistory(savedUser, "REGISTER", getClientIp(httpRequest), getUserAgent(httpRequest), true, null); - entityManager.flush(); // Explicitly flush to ensure history is persisted - - AuthResponse response = new AuthResponse(); - response.setToken(token); - response.setUserId(savedUser.getId()); - response.setEmail(savedUser.getEmail()); - response.setName(savedUser.getName()); - response.setExpiresAt(expiresAt); - - return response; - } - - public AuthResponse signupClient(RegisterRequest request, HttpServletRequest httpRequest) { - if (userRepository.existsByEmail(request.getEmail())) { - recordAuthHistory(null, "CLIENT_SIGNUP", getClientIp(httpRequest), getUserAgent(httpRequest), false, "Email already exists"); - throw new IllegalArgumentException("Client with email " + request.getEmail() + " already exists"); - } - - User user = new User(); - user.setId(CuidGenerator.generate()); - user.setName(request.getName()); - user.setEmail(request.getEmail()); - user.setPassword(passwordEncoder.encode(request.getPassword())); - - User savedUser = userRepository.save(user); - entityManager.flush(); // Explicitly flush to ensure user is persisted - - String token = jwtUtil.generateToken(savedUser.getId(), savedUser.getEmail()); - LocalDateTime expiresAt = LocalDateTime.now().plusHours(24); - - AuthenticationHistory history = recordAuthHistory(savedUser, "CLIENT_SIGNUP", getClientIp(httpRequest), getUserAgent(httpRequest), true, null); - entityManager.flush(); // Explicitly flush to ensure history is persisted - - AuthResponse response = new AuthResponse(); - response.setToken(token); - response.setUserId(savedUser.getId()); - response.setEmail(savedUser.getEmail()); - response.setName(savedUser.getName()); - response.setExpiresAt(expiresAt); - - return response; - } - - public AuthResponse login(LoginRequest request, HttpServletRequest httpRequest) { - User user = userRepository.findByEmail(request.getEmail()) - .orElseGet(() -> { - recordAuthHistory(null, "LOGIN", getClientIp(httpRequest), getUserAgent(httpRequest), false, "User not found"); - return null; - }); - - if (user == null || user.getPassword() == null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) { - // For security, use the same error message for both user not found and invalid password - // to prevent user enumeration attacks - if (user != null) { - recordAuthHistory(user, "LOGIN", getClientIp(httpRequest), getUserAgent(httpRequest), false, "Invalid password"); - } - throw new BadCredentialsException("Invalid email or password"); - } - - String token = jwtUtil.generateToken(user.getId(), user.getEmail()); - String tokenId = jwtUtil.extractTokenId(token); - LocalDateTime expiresAt = LocalDateTime.now().plusHours(24); - - AuthenticationHistory history = recordAuthHistory(user, "LOGIN", getClientIp(httpRequest), getUserAgent(httpRequest), true, null); - history.setTokenId(tokenId); - authHistoryRepository.save(history); - - AuthResponse response = new AuthResponse(); - response.setToken(token); - response.setUserId(user.getId()); - response.setEmail(user.getEmail()); - response.setName(user.getName()); - response.setExpiresAt(expiresAt); - - return response; - } - - public void validateToken(String token, HttpServletRequest httpRequest) { - try { - if (!jwtUtil.validateToken(token)) { - String tokenId = jwtUtil.extractTokenId(token); - String userId = jwtUtil.extractUserId(token); - User user = userRepository.findById(userId).orElse(null); - recordAuthHistory(user, "TOKEN_VALIDATED", getClientIp(httpRequest), getUserAgent(httpRequest), false, "Token is invalid or expired"); - throw new IllegalArgumentException("Invalid or expired token"); - } - - String userId = jwtUtil.extractUserId(token); - String tokenId = jwtUtil.extractTokenId(token); - User user = userRepository.findById(userId) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", userId)); - - recordAuthHistory(user, "TOKEN_VALIDATED", getClientIp(httpRequest), getUserAgent(httpRequest), true, null); - } catch (Exception e) { - throw new IllegalArgumentException("Token validation failed: " + e.getMessage()); - } - } - - @Transactional(readOnly = true) - public List getAuthHistory(String userId) { - return authHistoryRepository.findByUserIdOrderByCreatedAtDesc(userId) - .stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Transactional(readOnly = true) - public List getAuthHistoryByAction(String userId, String action) { - return authHistoryRepository.findByUserIdAndActionOrderByCreatedAtDesc(userId, action) - .stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - private AuthenticationHistory recordAuthHistory(User user, String action, String ipAddress, String userAgent, Boolean success, String failureReason) { - AuthenticationHistory history = new AuthenticationHistory(); - history.setId(CuidGenerator.generate()); - history.setUser(user); - history.setAction(action); - history.setIpAddress(ipAddress); - history.setUserAgent(userAgent); - history.setSuccess(success); - history.setFailureReason(failureReason); - return authHistoryRepository.save(history); - } - - private String getClientIp(HttpServletRequest request) { - String xForwardedFor = request.getHeader("X-Forwarded-For"); - if (xForwardedFor != null && !xForwardedFor.isEmpty()) { - return xForwardedFor.split(",")[0].trim(); - } - String xRealIp = request.getHeader("X-Real-IP"); - if (xRealIp != null && !xRealIp.isEmpty()) { - return xRealIp; - } - return request.getRemoteAddr(); - } - - private String getUserAgent(HttpServletRequest request) { - String userAgent = request.getHeader("User-Agent"); - if (userAgent != null && userAgent.length() > 500) { - return userAgent.substring(0, 500); - } - return userAgent; - } - - private AuthenticationHistoryDto convertToDto(AuthenticationHistory history) { - AuthenticationHistoryDto dto = new AuthenticationHistoryDto(); - dto.setId(history.getId()); - dto.setUserId(history.getUser() != null ? history.getUser().getId() : null); - dto.setAction(history.getAction()); - dto.setIpAddress(history.getIpAddress()); - dto.setUserAgent(history.getUserAgent()); - dto.setTokenId(history.getTokenId()); - dto.setSuccess(history.getSuccess()); - dto.setFailureReason(history.getFailureReason()); - dto.setCreatedAt(history.getCreatedAt()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/EventService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/EventService.java deleted file mode 100644 index 146b286..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/EventService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateEventRequest; -import com.scoop.hackathon.dto.EventDto; -import com.scoop.hackathon.dto.UpdateEventRequest; - -import java.util.List; - -public interface EventService { - EventDto createEvent(CreateEventRequest request); - EventDto getEventById(String id); - List getAllEvents(); - List getEventsByUserId(String userId); - List getEventsByGitSessionId(String gitSessionId); - List getEventsByType(String type); - EventDto updateEvent(String id, UpdateEventRequest request); - void deleteEvent(String id); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/EventServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/EventServiceImpl.java deleted file mode 100644 index 6b76c79..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/EventServiceImpl.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateEventRequest; -import com.scoop.hackathon.dto.EventDto; -import com.scoop.hackathon.dto.UpdateEventRequest; -import com.scoop.hackathon.entity.Event; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.EventRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class EventServiceImpl implements EventService { - - private final EventRepository eventRepository; - - public EventServiceImpl(EventRepository eventRepository) { - this.eventRepository = eventRepository; - } - - @Override - public EventDto createEvent(CreateEventRequest request) { - Event event = new Event(); - event.setId(CuidGenerator.generate()); - event.setType(request.getType()); - event.setUserId(request.getUserId()); - event.setGitSessionId(request.getGitSessionId()); - event.setMetadata(request.getMetadata()); - - Event savedEvent = eventRepository.save(event); - return convertToDto(savedEvent); - } - - @Override - @Transactional(readOnly = true) - public EventDto getEventById(String id) { - Event event = eventRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Event", "id", id)); - return convertToDto(event); - } - - @Override - @Transactional(readOnly = true) - public List getAllEvents() { - return eventRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getEventsByUserId(String userId) { - return eventRepository.findByUserId(userId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getEventsByGitSessionId(String gitSessionId) { - return eventRepository.findByGitSessionId(gitSessionId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getEventsByType(String type) { - return eventRepository.findByType(type).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public EventDto updateEvent(String id, UpdateEventRequest request) { - Event event = eventRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Event", "id", id)); - - if (request.getType() != null) event.setType(request.getType()); - if (request.getUserId() != null) event.setUserId(request.getUserId()); - if (request.getGitSessionId() != null) event.setGitSessionId(request.getGitSessionId()); - if (request.getMetadata() != null) event.setMetadata(request.getMetadata()); - - Event updatedEvent = eventRepository.save(event); - return convertToDto(updatedEvent); - } - - @Override - public void deleteEvent(String id) { - Event event = eventRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Event", "id", id)); - eventRepository.delete(event); - } - - private EventDto convertToDto(Event event) { - EventDto dto = new EventDto(); - dto.setId(event.getId()); - dto.setCreatedAt(event.getCreatedAt()); - dto.setType(event.getType()); - dto.setUserId(event.getUserId()); - dto.setGitSessionId(event.getGitSessionId()); - dto.setMetadata(event.getMetadata()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/GitSessionService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/GitSessionService.java deleted file mode 100644 index e455bee..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/GitSessionService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateGitSessionRequest; -import com.scoop.hackathon.dto.GitSessionDto; - -import java.util.List; - -public interface GitSessionService { - GitSessionDto createGitSession(CreateGitSessionRequest request); - GitSessionDto getGitSessionById(String id); - List getAllGitSessions(); - List getGitSessionsByUserId(String userId); - GitSessionDto updateGitSession(String id, CreateGitSessionRequest request); - void deleteGitSession(String id); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/GitSessionServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/GitSessionServiceImpl.java deleted file mode 100644 index 96a6058..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/GitSessionServiceImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateGitSessionRequest; -import com.scoop.hackathon.dto.GitSessionDto; -import com.scoop.hackathon.entity.GitSession; -import com.scoop.hackathon.entity.User; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.GitSessionRepository; -import com.scoop.hackathon.repository.UserRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class GitSessionServiceImpl implements GitSessionService { - - private final GitSessionRepository gitSessionRepository; - - private final UserRepository userRepository; - - public GitSessionServiceImpl(GitSessionRepository gitSessionRepository, UserRepository userRepository) { - this.gitSessionRepository = gitSessionRepository; - this.userRepository = userRepository; - } - - @Override - public GitSessionDto createGitSession(CreateGitSessionRequest request) { - GitSession gitSession = new GitSession(); - gitSession.setId(CuidGenerator.generate()); - gitSession.setTitle(request.getTitle()); - gitSession.setOs(request.getOs()); - gitSession.setRepoRootHash(request.getRepoRootHash()); - - if (request.getUserId() != null) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", request.getUserId())); - gitSession.setUser(user); - } - - GitSession savedSession = gitSessionRepository.save(gitSession); - return convertToDto(savedSession); - } - - @Override - @Transactional(readOnly = true) - public GitSessionDto getGitSessionById(String id) { - GitSession gitSession = gitSessionRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", id)); - return convertToDto(gitSession); - } - - @Override - @Transactional(readOnly = true) - public List getAllGitSessions() { - return gitSessionRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getGitSessionsByUserId(String userId) { - return gitSessionRepository.findByUserIdOrderByCreatedAtDesc(userId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public GitSessionDto updateGitSession(String id, CreateGitSessionRequest request) { - GitSession gitSession = gitSessionRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", id)); - - if (request.getTitle() != null) { - gitSession.setTitle(request.getTitle()); - } - if (request.getOs() != null) { - gitSession.setOs(request.getOs()); - } - if (request.getRepoRootHash() != null) { - gitSession.setRepoRootHash(request.getRepoRootHash()); - } - if (request.getUserId() != null) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", request.getUserId())); - gitSession.setUser(user); - } - - GitSession updatedSession = gitSessionRepository.save(gitSession); - return convertToDto(updatedSession); - } - - @Override - public void deleteGitSession(String id) { - GitSession gitSession = gitSessionRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", id)); - gitSessionRepository.delete(gitSession); - } - - private GitSessionDto convertToDto(GitSession gitSession) { - GitSessionDto dto = new GitSessionDto(); - dto.setId(gitSession.getId()); - dto.setTitle(gitSession.getTitle()); - dto.setOs(gitSession.getOs()); - dto.setRepoRootHash(gitSession.getRepoRootHash()); - dto.setUserId(gitSession.getUser() != null ? gitSession.getUser().getId() : null); - dto.setCreatedAt(gitSession.getCreatedAt()); - dto.setUpdatedAt(gitSession.getUpdatedAt()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/PlanService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/PlanService.java deleted file mode 100644 index e1522ca..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/PlanService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreatePlanRequest; -import com.scoop.hackathon.dto.PlanDto; -import com.scoop.hackathon.dto.UpdatePlanRequest; - -import java.util.List; - -public interface PlanService { - PlanDto createPlan(CreatePlanRequest request); - PlanDto getPlanById(String id); - List getAllPlans(); - List getPlansByGitSessionId(String gitSessionId); - PlanDto updatePlan(String id, UpdatePlanRequest request); - void deletePlan(String id); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/PlanServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/PlanServiceImpl.java deleted file mode 100644 index a29caec..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/PlanServiceImpl.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreatePlanRequest; -import com.scoop.hackathon.dto.PlanDto; -import com.scoop.hackathon.dto.UpdatePlanRequest; -import com.scoop.hackathon.entity.GitSession; -import com.scoop.hackathon.entity.Plan; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.GitSessionRepository; -import com.scoop.hackathon.repository.PlanRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class PlanServiceImpl implements PlanService { - - private final PlanRepository planRepository; - private final GitSessionRepository gitSessionRepository; - - public PlanServiceImpl(PlanRepository planRepository, GitSessionRepository gitSessionRepository) { - this.planRepository = planRepository; - this.gitSessionRepository = gitSessionRepository; - } - - @Override - public PlanDto createPlan(CreatePlanRequest request) { - GitSession gitSession = gitSessionRepository.findById(request.getGitSessionId()) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", request.getGitSessionId())); - - Plan plan = new Plan(); - plan.setId(CuidGenerator.generate()); - plan.setGitSession(gitSession); - plan.setPlanJson(request.getPlanJson()); - plan.setIssueType(request.getIssueType()); - plan.setRisk(request.getRisk()); - plan.setDangerousAllowed(request.getDangerousAllowed() != null ? request.getDangerousAllowed() : false); - - Plan savedPlan = planRepository.save(plan); - return convertToDto(savedPlan); - } - - @Override - @Transactional(readOnly = true) - public PlanDto getPlanById(String id) { - Plan plan = planRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Plan", "id", id)); - return convertToDto(plan); - } - - @Override - @Transactional(readOnly = true) - public List getAllPlans() { - return planRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getPlansByGitSessionId(String gitSessionId) { - return planRepository.findByGitSessionIdOrderByCreatedAtDesc(gitSessionId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public PlanDto updatePlan(String id, UpdatePlanRequest request) { - Plan plan = planRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Plan", "id", id)); - - if (request.getIssueType() != null) plan.setIssueType(request.getIssueType()); - if (request.getRisk() != null) plan.setRisk(request.getRisk()); - if (request.getPlanJson() != null) plan.setPlanJson(request.getPlanJson()); - if (request.getDangerousAllowed() != null) plan.setDangerousAllowed(request.getDangerousAllowed()); - if (request.getGitSessionId() != null) { - GitSession gitSession = gitSessionRepository.findById(request.getGitSessionId()) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", request.getGitSessionId())); - plan.setGitSession(gitSession); - } - - Plan updatedPlan = planRepository.save(plan); - return convertToDto(updatedPlan); - } - - @Override - public void deletePlan(String id) { - Plan plan = planRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Plan", "id", id)); - planRepository.delete(plan); - } - - private PlanDto convertToDto(Plan plan) { - PlanDto dto = new PlanDto(); - dto.setId(plan.getId()); - dto.setCreatedAt(plan.getCreatedAt()); - dto.setGitSessionId(plan.getGitSession() != null ? plan.getGitSession().getId() : null); - dto.setIssueType(plan.getIssueType()); - dto.setRisk(plan.getRisk()); - dto.setPlanJson(plan.getPlanJson()); - dto.setDangerousAllowed(plan.getDangerousAllowed()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SessionService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SessionService.java deleted file mode 100644 index 639ebac..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SessionService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateSessionRequest; -import com.scoop.hackathon.dto.SessionDto; -import com.scoop.hackathon.dto.UpdateSessionRequest; - -import java.util.List; - -public interface SessionService { - SessionDto createSession(CreateSessionRequest request); - SessionDto getSessionById(String id); - SessionDto getSessionByToken(String token); - List getAllSessions(); - List getSessionsByUserId(String userId); - SessionDto updateSession(String id, UpdateSessionRequest request); - void deleteSession(String id); - void deleteSessionByToken(String token); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SessionServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SessionServiceImpl.java deleted file mode 100644 index d8913c5..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SessionServiceImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateSessionRequest; -import com.scoop.hackathon.dto.SessionDto; -import com.scoop.hackathon.dto.UpdateSessionRequest; -import com.scoop.hackathon.entity.Session; -import com.scoop.hackathon.entity.User; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.SessionRepository; -import com.scoop.hackathon.repository.UserRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class SessionServiceImpl implements SessionService { - - private final SessionRepository sessionRepository; - private final UserRepository userRepository; - - public SessionServiceImpl(SessionRepository sessionRepository, UserRepository userRepository) { - this.sessionRepository = sessionRepository; - this.userRepository = userRepository; - } - - @Override - public SessionDto createSession(CreateSessionRequest request) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", request.getUserId())); - - Session session = new Session(); - session.setId(CuidGenerator.generate()); - session.setSessionToken(request.getSessionToken()); - session.setExpires(request.getExpires()); - session.setUser(user); - - Session savedSession = sessionRepository.save(session); - return convertToDto(savedSession); - } - - @Override - @Transactional(readOnly = true) - public SessionDto getSessionById(String id) { - Session session = sessionRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Session", "id", id)); - return convertToDto(session); - } - - @Override - @Transactional(readOnly = true) - public SessionDto getSessionByToken(String token) { - Session session = sessionRepository.findBySessionToken(token) - .orElseThrow(() -> new ResourceNotFoundException("Session", "token", token)); - return convertToDto(session); - } - - @Override - @Transactional(readOnly = true) - public List getAllSessions() { - return sessionRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getSessionsByUserId(String userId) { - return sessionRepository.findByUserId(userId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public SessionDto updateSession(String id, UpdateSessionRequest request) { - Session session = sessionRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Session", "id", id)); - - if (request.getSessionToken() != null) session.setSessionToken(request.getSessionToken()); - if (request.getExpires() != null) session.setExpires(request.getExpires()); - if (request.getUserId() != null) { - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", request.getUserId())); - session.setUser(user); - } - - Session updatedSession = sessionRepository.save(session); - return convertToDto(updatedSession); - } - - @Override - public void deleteSession(String id) { - Session session = sessionRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Session", "id", id)); - sessionRepository.delete(session); - } - - @Override - public void deleteSessionByToken(String token) { - sessionRepository.deleteBySessionToken(token); - } - - private SessionDto convertToDto(Session session) { - SessionDto dto = new SessionDto(); - dto.setId(session.getId()); - dto.setSessionToken(session.getSessionToken()); - dto.setExpires(session.getExpires()); - dto.setUserId(session.getUser() != null ? session.getUser().getId() : null); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SnapshotService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SnapshotService.java deleted file mode 100644 index 4f539bc..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SnapshotService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateSnapshotRequest; -import com.scoop.hackathon.dto.SnapshotDto; -import com.scoop.hackathon.dto.UpdateSnapshotRequest; - -import java.util.List; - -public interface SnapshotService { - SnapshotDto createSnapshot(CreateSnapshotRequest request); - SnapshotDto getSnapshotById(String id); - List getAllSnapshots(); - List getSnapshotsByGitSessionId(String gitSessionId); - SnapshotDto updateSnapshot(String id, UpdateSnapshotRequest request); - void deleteSnapshot(String id); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SnapshotServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SnapshotServiceImpl.java deleted file mode 100644 index ed61286..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/SnapshotServiceImpl.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateSnapshotRequest; -import com.scoop.hackathon.dto.SnapshotDto; -import com.scoop.hackathon.dto.UpdateSnapshotRequest; -import com.scoop.hackathon.entity.GitSession; -import com.scoop.hackathon.entity.Snapshot; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.GitSessionRepository; -import com.scoop.hackathon.repository.SnapshotRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class SnapshotServiceImpl implements SnapshotService { - - private final SnapshotRepository snapshotRepository; - private final GitSessionRepository gitSessionRepository; - - public SnapshotServiceImpl(SnapshotRepository snapshotRepository, GitSessionRepository gitSessionRepository) { - this.snapshotRepository = snapshotRepository; - this.gitSessionRepository = gitSessionRepository; - } - - @Override - public SnapshotDto createSnapshot(CreateSnapshotRequest request) { - GitSession gitSession = gitSessionRepository.findById(request.getGitSessionId()) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", request.getGitSessionId())); - - Snapshot snapshot = new Snapshot(); - snapshot.setId(CuidGenerator.generate()); - snapshot.setGitSession(gitSession); - snapshot.setSnapshotJson(request.getSnapshotJson()); - snapshot.setTruncated(request.getTruncated() != null ? request.getTruncated() : false); - - Snapshot savedSnapshot = snapshotRepository.save(snapshot); - return convertToDto(savedSnapshot); - } - - @Override - @Transactional(readOnly = true) - public SnapshotDto getSnapshotById(String id) { - Snapshot snapshot = snapshotRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Snapshot", "id", id)); - return convertToDto(snapshot); - } - - @Override - @Transactional(readOnly = true) - public List getAllSnapshots() { - return snapshotRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getSnapshotsByGitSessionId(String gitSessionId) { - return snapshotRepository.findByGitSessionIdOrderByCreatedAtDesc(gitSessionId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public SnapshotDto updateSnapshot(String id, UpdateSnapshotRequest request) { - Snapshot snapshot = snapshotRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Snapshot", "id", id)); - - if (request.getSnapshotJson() != null) snapshot.setSnapshotJson(request.getSnapshotJson()); - if (request.getTruncated() != null) snapshot.setTruncated(request.getTruncated()); - if (request.getGitSessionId() != null) { - GitSession gitSession = gitSessionRepository.findById(request.getGitSessionId()) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", request.getGitSessionId())); - snapshot.setGitSession(gitSession); - } - - Snapshot updatedSnapshot = snapshotRepository.save(snapshot); - return convertToDto(updatedSnapshot); - } - - @Override - public void deleteSnapshot(String id) { - Snapshot snapshot = snapshotRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Snapshot", "id", id)); - snapshotRepository.delete(snapshot); - } - - private SnapshotDto convertToDto(Snapshot snapshot) { - SnapshotDto dto = new SnapshotDto(); - dto.setId(snapshot.getId()); - dto.setCreatedAt(snapshot.getCreatedAt()); - dto.setGitSessionId(snapshot.getGitSession() != null ? snapshot.getGitSession().getId() : null); - dto.setSnapshotJson(snapshot.getSnapshotJson()); - dto.setTruncated(snapshot.getTruncated()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/TraceService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/TraceService.java deleted file mode 100644 index 6e2815f..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/TraceService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateTraceRequest; -import com.scoop.hackathon.dto.TraceDto; -import com.scoop.hackathon.dto.UpdateTraceRequest; - -import java.util.List; - -public interface TraceService { - TraceDto createTrace(CreateTraceRequest request); - TraceDto getTraceById(String id); - List getAllTraces(); - List getTracesByGitSessionId(String gitSessionId); - List getTracesBySnapshotId(String snapshotId); - TraceDto updateTrace(String id, UpdateTraceRequest request); - void deleteTrace(String id); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/TraceServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/TraceServiceImpl.java deleted file mode 100644 index 0b94cf3..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/TraceServiceImpl.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateTraceRequest; -import com.scoop.hackathon.dto.TraceDto; -import com.scoop.hackathon.dto.UpdateTraceRequest; -import com.scoop.hackathon.entity.GitSession; -import com.scoop.hackathon.entity.Snapshot; -import com.scoop.hackathon.entity.Trace; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.GitSessionRepository; -import com.scoop.hackathon.repository.SnapshotRepository; -import com.scoop.hackathon.repository.TraceRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class TraceServiceImpl implements TraceService { - - private final TraceRepository traceRepository; - private final GitSessionRepository gitSessionRepository; - private final SnapshotRepository snapshotRepository; - - public TraceServiceImpl(TraceRepository traceRepository, GitSessionRepository gitSessionRepository, SnapshotRepository snapshotRepository) { - this.traceRepository = traceRepository; - this.gitSessionRepository = gitSessionRepository; - this.snapshotRepository = snapshotRepository; - } - - @Override - public TraceDto createTrace(CreateTraceRequest request) { - GitSession gitSession = gitSessionRepository.findById(request.getGitSessionId()) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", request.getGitSessionId())); - - Trace trace = new Trace(); - trace.setId(CuidGenerator.generate()); - trace.setGitSession(gitSession); - trace.setStage(request.getStage()); - trace.setOutputJson(request.getOutputJson()); - trace.setDurationMs(request.getDurationMs()); - trace.setSuccess(request.getSuccess() != null ? request.getSuccess() : true); - trace.setErrorMessage(request.getErrorMessage()); - - if (request.getSnapshotId() != null) { - Snapshot snapshot = snapshotRepository.findById(request.getSnapshotId()) - .orElseThrow(() -> new ResourceNotFoundException("Snapshot", "id", request.getSnapshotId())); - trace.setSnapshot(snapshot); - } - - Trace savedTrace = traceRepository.save(trace); - return convertToDto(savedTrace); - } - - @Override - @Transactional(readOnly = true) - public TraceDto getTraceById(String id) { - Trace trace = traceRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Trace", "id", id)); - return convertToDto(trace); - } - - @Override - @Transactional(readOnly = true) - public List getAllTraces() { - return traceRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getTracesByGitSessionId(String gitSessionId) { - return traceRepository.findByGitSessionIdOrderByCreatedAtDesc(gitSessionId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - @Transactional(readOnly = true) - public List getTracesBySnapshotId(String snapshotId) { - return traceRepository.findBySnapshotId(snapshotId).stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public TraceDto updateTrace(String id, UpdateTraceRequest request) { - Trace trace = traceRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Trace", "id", id)); - - if (request.getStage() != null) trace.setStage(request.getStage()); - if (request.getOutputJson() != null) trace.setOutputJson(request.getOutputJson()); - if (request.getDurationMs() != null) trace.setDurationMs(request.getDurationMs()); - if (request.getSuccess() != null) trace.setSuccess(request.getSuccess()); - if (request.getErrorMessage() != null) trace.setErrorMessage(request.getErrorMessage()); - if (request.getGitSessionId() != null) { - GitSession gitSession = gitSessionRepository.findById(request.getGitSessionId()) - .orElseThrow(() -> new ResourceNotFoundException("GitSession", "id", request.getGitSessionId())); - trace.setGitSession(gitSession); - } - if (request.getSnapshotId() != null) { - Snapshot snapshot = snapshotRepository.findById(request.getSnapshotId()) - .orElseThrow(() -> new ResourceNotFoundException("Snapshot", "id", request.getSnapshotId())); - trace.setSnapshot(snapshot); - } - - Trace updatedTrace = traceRepository.save(trace); - return convertToDto(updatedTrace); - } - - @Override - public void deleteTrace(String id) { - Trace trace = traceRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("Trace", "id", id)); - traceRepository.delete(trace); - } - - private TraceDto convertToDto(Trace trace) { - TraceDto dto = new TraceDto(); - dto.setId(trace.getId()); - dto.setCreatedAt(trace.getCreatedAt()); - dto.setGitSessionId(trace.getGitSession() != null ? trace.getGitSession().getId() : null); - dto.setStage(trace.getStage()); - dto.setSnapshotId(trace.getSnapshot() != null ? trace.getSnapshot().getId() : null); - dto.setOutputJson(trace.getOutputJson()); - dto.setDurationMs(trace.getDurationMs()); - dto.setSuccess(trace.getSuccess()); - dto.setErrorMessage(trace.getErrorMessage()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/UserService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/UserService.java deleted file mode 100644 index c51af7c..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/UserService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateUserRequest; -import com.scoop.hackathon.dto.UpdateUserRequest; -import com.scoop.hackathon.dto.UserDto; - -import java.util.List; - -public interface UserService { - UserDto createUser(CreateUserRequest request); - UserDto getUserById(String id); - UserDto getUserByEmail(String email); - List getAllUsers(); - UserDto updateUser(String id, UpdateUserRequest request); - void deleteUser(String id); - boolean existsByEmail(String email); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/UserServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/UserServiceImpl.java deleted file mode 100644 index 421fcd4..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/UserServiceImpl.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateUserRequest; -import com.scoop.hackathon.dto.UpdateUserRequest; -import com.scoop.hackathon.dto.UserDto; -import com.scoop.hackathon.entity.User; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.UserRepository; -import com.scoop.hackathon.util.CuidGenerator; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class UserServiceImpl implements UserService { - - private final UserRepository userRepository; - - public UserServiceImpl(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Override - public UserDto createUser(CreateUserRequest request) { - if (userRepository.existsByEmail(request.getEmail())) { - throw new IllegalArgumentException("User with email " + request.getEmail() + " already exists"); - } - - User user = new User(); - user.setId(CuidGenerator.generate()); - user.setName(request.getName()); - user.setEmail(request.getEmail()); - user.setPassword(request.getPassword()); - user.setImage(request.getImage()); - - User savedUser = userRepository.save(user); - return convertToDto(savedUser); - } - - @Override - @Transactional(readOnly = true) - public UserDto getUserById(String id) { - User user = userRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", id)); - return convertToDto(user); - } - - @Override - @Transactional(readOnly = true) - public UserDto getUserByEmail(String email) { - User user = userRepository.findByEmail(email) - .orElseThrow(() -> new ResourceNotFoundException("User", "email", email)); - return convertToDto(user); - } - - @Override - @Transactional(readOnly = true) - public List getAllUsers() { - return userRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public UserDto updateUser(String id, UpdateUserRequest request) { - User user = userRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", id)); - - if (request.getName() != null) { - user.setName(request.getName()); - } - if (request.getEmail() != null) { - if (userRepository.existsByEmail(request.getEmail()) && !user.getEmail().equals(request.getEmail())) { - throw new IllegalArgumentException("Email already exists"); - } - user.setEmail(request.getEmail()); - } - if (request.getImage() != null) { - user.setImage(request.getImage()); - } - - User updatedUser = userRepository.save(user); - return convertToDto(updatedUser); - } - - @Override - public void deleteUser(String id) { - User user = userRepository.findById(id) - .orElseThrow(() -> new ResourceNotFoundException("User", "id", id)); - userRepository.delete(user); - } - - @Override - @Transactional(readOnly = true) - public boolean existsByEmail(String email) { - return userRepository.existsByEmail(email); - } - - private UserDto convertToDto(User user) { - UserDto dto = new UserDto(); - dto.setId(user.getId()); - dto.setName(user.getName()); - dto.setEmail(user.getEmail()); - dto.setEmailVerified(user.getEmailVerified()); - dto.setImage(user.getImage()); - dto.setCreatedAt(user.getCreatedAt()); - dto.setUpdatedAt(user.getUpdatedAt()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/VerificationTokenService.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/VerificationTokenService.java deleted file mode 100644 index 6b3b287..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/VerificationTokenService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateVerificationTokenRequest; -import com.scoop.hackathon.dto.UpdateVerificationTokenRequest; -import com.scoop.hackathon.dto.VerificationTokenDto; - -import java.util.List; - -public interface VerificationTokenService { - VerificationTokenDto createVerificationToken(CreateVerificationTokenRequest request); - VerificationTokenDto getVerificationTokenById(String identifier); - VerificationTokenDto getVerificationTokenByToken(String token); - List getAllVerificationTokens(); - VerificationTokenDto updateVerificationToken(String identifier, UpdateVerificationTokenRequest request); - void deleteVerificationToken(String identifier); - void deleteVerificationTokenByToken(String token); -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/VerificationTokenServiceImpl.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/VerificationTokenServiceImpl.java deleted file mode 100644 index e914674..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/service/VerificationTokenServiceImpl.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.scoop.hackathon.service; - -import com.scoop.hackathon.dto.CreateVerificationTokenRequest; -import com.scoop.hackathon.dto.UpdateVerificationTokenRequest; -import com.scoop.hackathon.dto.VerificationTokenDto; -import com.scoop.hackathon.entity.VerificationToken; -import com.scoop.hackathon.exception.ResourceNotFoundException; -import com.scoop.hackathon.repository.VerificationTokenRepository; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Transactional -public class VerificationTokenServiceImpl implements VerificationTokenService { - - private final VerificationTokenRepository verificationTokenRepository; - - public VerificationTokenServiceImpl(VerificationTokenRepository verificationTokenRepository) { - this.verificationTokenRepository = verificationTokenRepository; - } - - @Override - public VerificationTokenDto createVerificationToken(CreateVerificationTokenRequest request) { - VerificationToken token = new VerificationToken(); - token.setIdentifier(request.getIdentifier()); - token.setToken(request.getToken()); - token.setExpires(request.getExpires()); - - VerificationToken savedToken = verificationTokenRepository.save(token); - return convertToDto(savedToken); - } - - @Override - @Transactional(readOnly = true) - public VerificationTokenDto getVerificationTokenById(String identifier) { - VerificationToken token = verificationTokenRepository.findById(identifier) - .orElseThrow(() -> new ResourceNotFoundException("VerificationToken", "identifier", identifier)); - return convertToDto(token); - } - - @Override - @Transactional(readOnly = true) - public VerificationTokenDto getVerificationTokenByToken(String token) { - VerificationToken verificationToken = verificationTokenRepository.findByToken(token) - .orElseThrow(() -> new ResourceNotFoundException("VerificationToken", "token", token)); - return convertToDto(verificationToken); - } - - @Override - @Transactional(readOnly = true) - public List getAllVerificationTokens() { - return verificationTokenRepository.findAll().stream() - .map(this::convertToDto) - .collect(Collectors.toList()); - } - - @Override - public VerificationTokenDto updateVerificationToken(String identifier, UpdateVerificationTokenRequest request) { - VerificationToken token = verificationTokenRepository.findById(identifier) - .orElseThrow(() -> new ResourceNotFoundException("VerificationToken", "identifier", identifier)); - - if (request.getToken() != null) token.setToken(request.getToken()); - if (request.getExpires() != null) token.setExpires(request.getExpires()); - - VerificationToken updatedToken = verificationTokenRepository.save(token); - return convertToDto(updatedToken); - } - - @Override - public void deleteVerificationToken(String identifier) { - VerificationToken token = verificationTokenRepository.findById(identifier) - .orElseThrow(() -> new ResourceNotFoundException("VerificationToken", "identifier", identifier)); - verificationTokenRepository.delete(token); - } - - @Override - public void deleteVerificationTokenByToken(String token) { - verificationTokenRepository.deleteByToken(token); - } - - private VerificationTokenDto convertToDto(VerificationToken token) { - VerificationTokenDto dto = new VerificationTokenDto(); - dto.setIdentifier(token.getIdentifier()); - dto.setToken(token.getToken()); - dto.setExpires(token.getExpires()); - return dto; - } -} - diff --git a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/util/CuidGenerator.java b/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/util/CuidGenerator.java deleted file mode 100644 index 61290d9..0000000 --- a/apps/scoop-hackathon/src/main/java/com/scoop/hackathon/util/CuidGenerator.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.scoop.hackathon.util; - -import java.util.UUID; - -public class CuidGenerator { - public static String generate() { - // Simple CUID-like generator using UUID - // For production, consider using a proper CUID library - return "c" + UUID.randomUUID().toString().replace("-", "").substring(0, 24); - } -} - diff --git a/apps/scoop-hackathon/src/main/resources/application.properties b/apps/scoop-hackathon/src/main/resources/application.properties deleted file mode 100644 index 8bd7946..0000000 --- a/apps/scoop-hackathon/src/main/resources/application.properties +++ /dev/null @@ -1,131 +0,0 @@ -# ============================================================================ -# Application Configuration -# ============================================================================ -# Values are loaded from .env file or environment variables -# See .env.example for all available configuration options -# -# Environment-specific configurations are available: -# - application-local.properties (for local development) -# - application-dev.properties (for development/staging) -# - application-prod.properties (for production) -# -# Activate a profile using: -# - Environment variable: SPRING_PROFILES_ACTIVE=local -# - Command line: --spring.profiles.active=local -# - IDE run configuration: Add VM option -Dspring.profiles.active=local -# -# Default profile is used if no profile is specified -spring.application.name=${APP_NAME:scoop-hackathon} -server.port=${SERVER_PORT:8080} - -# ============================================================================ -# Database Configuration - MySQL -# ============================================================================ -spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/gitguard_agent?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true} -spring.datasource.driver-class-name=${DB_DRIVER:com.mysql.cj.jdbc.Driver} -spring.datasource.username=${DB_USERNAME:root} -spring.datasource.password=${DB_PASSWORD:} - -# ============================================================================ -# JPA/Hibernate Configuration -# ============================================================================ -spring.jpa.hibernate.ddl-auto=${JPA_DDL_AUTO:validate} -spring.jpa.show-sql=${JPA_SHOW_SQL:true} -spring.jpa.properties.hibernate.format_sql=${JPA_FORMAT_SQL:true} -spring.jpa.properties.hibernate.dialect=${JPA_DIALECT:org.hibernate.dialect.MySQLDialect} -spring.jpa.properties.hibernate.jdbc.time_zone=UTC - -# JPA Properties -spring.jpa.open-in-view=false -spring.jpa.properties.hibernate.use_sql_comments=true -spring.jpa.properties.hibernate.generate_statistics=false - -# Connection Pool Settings (HikariCP - default in Spring Boot) -spring.datasource.hikari.maximum-pool-size=10 -spring.datasource.hikari.minimum-idle=5 -spring.datasource.hikari.connection-timeout=20000 -spring.datasource.hikari.idle-timeout=300000 -spring.datasource.hikari.max-lifetime=1200000 -spring.datasource.hikari.leak-detection-threshold=60000 - -# MySQL-specific optimizations -spring.datasource.hikari.connection-test-query=SELECT 1 -# Temporarily disabled to fix transaction commit issues -# spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true -spring.jpa.properties.hibernate.jdbc.batch_size=20 -spring.jpa.properties.hibernate.order_inserts=true -spring.jpa.properties.hibernate.order_updates=true - -# ============================================================================ -# Flyway Database Migration Configuration -# ============================================================================ -spring.flyway.enabled=${FLYWAY_ENABLED:true} -spring.flyway.baseline-on-migrate=${FLYWAY_BASELINE_ON_MIGRATE:true} -spring.flyway.baseline-version=${FLYWAY_BASELINE_VERSION:0} -spring.flyway.locations=classpath:db/migration -spring.flyway.validate-on-migrate=${FLYWAY_VALIDATE_ON_MIGRATE:false} -spring.flyway.clean-disabled=true -spring.flyway.ignore-missing-migrations=true -spring.flyway.repair-on-migrate=true - -# ============================================================================ -# JSON/Jackson Configuration -# ============================================================================ -spring.jackson.serialization.write-dates-as-timestamps=false -spring.jackson.time-zone=UTC -spring.jackson.default-property-inclusion=non_null - -# ============================================================================ -# Logging Configuration -# ============================================================================ -logging.level.root=${LOG_LEVEL_ROOT:INFO} -logging.level.org.springframework.web=${LOG_LEVEL_WEB:INFO} -logging.level.org.hibernate.SQL=${LOG_LEVEL_HIBERNATE:DEBUG} -logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE -logging.level.com.scoop.hackathon=${LOG_LEVEL_APP:DEBUG} - -# ============================================================================ -# Server Configuration -# ============================================================================ -server.error.include-message=always -server.error.include-binding-errors=always -server.error.include-stacktrace=on_param -server.error.include-exception=false -server.compression.enabled=true -server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain - -# ============================================================================ -# JWT Configuration -# ============================================================================ -# JWT Secret Key (should be at least 32 characters, set via environment variable) -jwt.secret=${JWT_SECRET:defaultSecretKeyThatShouldBeChangedInProductionEnvironmentMinimum32Characters} -# JWT Expiration time in milliseconds (default: 86400000 = 24 hours) -jwt.expiration=${JWT_EXPIRATION:86400000} - -# ============================================================================ -# Swagger/OpenAPI Configuration -# ============================================================================ -# Enable/disable Swagger UI (default: true) -springdoc.api-docs.enabled=${SWAGGER_ENABLED:true} -springdoc.swagger-ui.enabled=${SWAGGER_UI_ENABLED:true} -# Swagger UI path (default: /swagger-ui.html) -springdoc.swagger-ui.path=/swagger-ui.html -# API docs path (default: /v3/api-docs) -springdoc.api-docs.path=/v3/api-docs -# Show operations in Swagger UI (default: true) -springdoc.swagger-ui.operationsSorter=method -springdoc.swagger-ui.tagsSorter=alpha -# Try it out enabled by default -springdoc.swagger-ui.tryItOutEnabled=true -# Show request duration -springdoc.swagger-ui.displayRequestDuration=true -# Filter endpoints -springdoc.swagger-ui.filter=true - -# ============================================================================ -# Web/UI Configuration -# ============================================================================ -# Favicon configuration - Spring Boot will look for favicon.ico in static resources -# Place favicon.ico in src/main/resources/static/ or src/main/resources/public/ -spring.web.resources.static-locations=classpath:/static/,classpath:/public/,classpath:/resources/,classpath:/META-INF/resources/ - diff --git a/apps/scoop-hackathon/src/main/resources/db/migration/V1__Initial_schema.sql b/apps/scoop-hackathon/src/main/resources/db/migration/V1__Initial_schema.sql deleted file mode 100644 index 7080fdc..0000000 --- a/apps/scoop-hackathon/src/main/resources/db/migration/V1__Initial_schema.sql +++ /dev/null @@ -1,139 +0,0 @@ --- Flyway migration script --- This file will be executed automatically when the application starts --- File naming convention: V{version}__{description}.sql - --- Disable foreign key checks temporarily to allow dropping tables in any order -SET FOREIGN_KEY_CHECKS = 0; - --- Drop existing tables if they exist (for clean migration) --- Drop in reverse dependency order (child tables first, then parent tables) -DROP TABLE IF EXISTS trace; -DROP TABLE IF EXISTS plan; -DROP TABLE IF EXISTS snapshot; -DROP TABLE IF EXISTS event; -DROP TABLE IF EXISTS git_session; -DROP TABLE IF EXISTS GitSession; -- Handle case sensitivity (Hibernate may have created this) -DROP TABLE IF EXISTS session; -DROP TABLE IF EXISTS account; -DROP TABLE IF EXISTS verification_token; -DROP TABLE IF EXISTS `user`; - --- Re-enable foreign key checks -SET FOREIGN_KEY_CHECKS = 1; - --- Create verification_token table -CREATE TABLE verification_token ( - identifier VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - token VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - expires TIMESTAMP NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create user table -CREATE TABLE `user` ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - email VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - email_verified TIMESTAMP, - image VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - password VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create account table -CREATE TABLE account ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - type VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - provider VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - provider_account_id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - refresh_token TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - access_token TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - expires_at INTEGER, - token_type VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - scope VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - id_token TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - session_state VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - user_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - CONSTRAINT fk_account_user FOREIGN KEY (user_id) REFERENCES `user`(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create session table -CREATE TABLE session ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - session_token VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE, - expires TIMESTAMP NOT NULL, - user_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - CONSTRAINT fk_session_user FOREIGN KEY (user_id) REFERENCES `user`(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create git_session table -CREATE TABLE git_session ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - title VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - os VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - repo_root_hash VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - user_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - CONSTRAINT fk_git_session_user FOREIGN KEY (user_id) REFERENCES `user`(id) ON DELETE SET NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create event table -CREATE TABLE event ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - type VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - user_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - git_session_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - metadata JSON -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create snapshot table -CREATE TABLE snapshot ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - git_session_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - snapshot_json JSON NOT NULL, - truncated BOOLEAN NOT NULL DEFAULT FALSE, - CONSTRAINT fk_snapshot_git_session FOREIGN KEY (git_session_id) REFERENCES git_session(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create trace table -CREATE TABLE trace ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - git_session_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - stage VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - snapshot_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - output_json JSON NOT NULL, - duration_ms INTEGER, - success BOOLEAN NOT NULL DEFAULT TRUE, - error_message VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - CONSTRAINT fk_trace_git_session FOREIGN KEY (git_session_id) REFERENCES git_session(id) ON DELETE CASCADE, - CONSTRAINT fk_trace_snapshot FOREIGN KEY (snapshot_id) REFERENCES snapshot(id) ON DELETE SET NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create plan table -CREATE TABLE plan ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - git_session_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - issue_type VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - risk VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - plan_json JSON NOT NULL, - dangerous_allowed BOOLEAN NOT NULL DEFAULT FALSE, - CONSTRAINT fk_plan_git_session FOREIGN KEY (git_session_id) REFERENCES git_session(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create indexes for better performance -CREATE INDEX idx_account_user_id ON account(user_id); -CREATE INDEX idx_session_user_id ON session(user_id); -CREATE INDEX idx_session_token ON session(session_token); -CREATE INDEX idx_git_session_user_id ON git_session(user_id); -CREATE INDEX idx_event_user_id ON event(user_id); -CREATE INDEX idx_event_git_session_id ON event(git_session_id); -CREATE INDEX idx_snapshot_git_session_id ON snapshot(git_session_id); -CREATE INDEX idx_trace_git_session_id ON trace(git_session_id); -CREATE INDEX idx_trace_snapshot_id ON trace(snapshot_id); -CREATE INDEX idx_plan_git_session_id ON plan(git_session_id); - diff --git a/apps/scoop-hackathon/src/main/resources/db/migration/V2__Create_authentication_history_table.sql b/apps/scoop-hackathon/src/main/resources/db/migration/V2__Create_authentication_history_table.sql deleted file mode 100644 index dd2bc68..0000000 --- a/apps/scoop-hackathon/src/main/resources/db/migration/V2__Create_authentication_history_table.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Create authentication_history table for JWT-based authentication tracking -CREATE TABLE authentication_history ( - id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY, - user_id VARCHAR(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - action VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, - ip_address VARCHAR(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - user_agent VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - token_id VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - success BOOLEAN NOT NULL, - failure_reason VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT fk_auth_history_user FOREIGN KEY (user_id) REFERENCES `user`(id) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - --- Create indexes for better performance -CREATE INDEX idx_auth_history_user_id ON authentication_history(user_id); -CREATE INDEX idx_auth_history_action ON authentication_history(action); -CREATE INDEX idx_auth_history_success ON authentication_history(success); -CREATE INDEX idx_auth_history_token_id ON authentication_history(token_id); -CREATE INDEX idx_auth_history_created_at ON authentication_history(created_at); - diff --git a/apps/web/package.json b/apps/web/package.json index 4ff247a..3150d29 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,13 +19,13 @@ "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "lucide-react": "^0.562.0", + "lucide-react": "^0.469.0", "nanoid": "^5.0.9", "next": "^15.1.3", "next-auth": "^5.0.0-beta.25", "react": "^19.0.0", "react-dom": "^19.0.0", - "tailwind-merge": "^3.4.0", + "tailwind-merge": "^2.6.0", "zod": "^3.24.1" }, "devDependencies": { diff --git a/apps/web/prisma/schema.prisma b/apps/web/prisma/schema.prisma index 6bb2177..f3f9e9b 100644 --- a/apps/web/prisma/schema.prisma +++ b/apps/web/prisma/schema.prisma @@ -1,4 +1,4 @@ -// Prisma schema for GitGuard Agent +// Prisma schema for GitGuard Agent - Git Incident Room // Using Neon Postgres as the database generator client { @@ -6,8 +6,8 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") } // ============================================ @@ -20,16 +20,34 @@ model User { email String? @unique emailVerified DateTime? image String? - password String? // For credentials provider + password String? // For credentials provider - accounts Account[] - sessions Session[] - gitSessions GitSession[] + accounts Account[] + sessions Session[] + gitSessions GitSession[] + authenticationLogs AuthenticationLog[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } +// Authentication audit logging +model AuthenticationLog { + id String @id @default(cuid()) + userId String? + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + action String // LOGIN, REGISTER, LOGOUT, LOGIN_FAILED, TOKEN_REFRESH + ipAddress String? + userAgent String? @db.Text + success Boolean + failureReason String? + createdAt DateTime @default(now()) + + @@index([userId]) + @@index([action]) + @@index([createdAt]) +} + model Account { id String @id @default(cuid()) userId String @@ -67,105 +85,195 @@ model VerificationToken { } // ============================================ -// GitGuard Application Tables +// GitGuard Application Tables - Git Incident Room // ============================================ +// Main session for a git incident model GitSession { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - title String? - os String? + title String? + os String? repoRootHash String? + status String @default("pending") // pending, analyzing, ready, error // Owner (nullable for anonymous/public uploads) - userId String? - user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + userId String? + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) // Relations - snapshots Snapshot[] - plans Plan[] - traces Trace[] + snapshots Snapshot[] + analyses Analysis[] + traces Trace[] @@index([userId]) @@index([createdAt]) + @@index([status]) } +// Raw snapshot from CLI model Snapshot { - id String @id @default(cuid()) - createdAt DateTime @default(now()) + id String @id @default(cuid()) + createdAt DateTime @default(now()) gitSessionId String gitSession GitSession @relation(fields: [gitSessionId], references: [id], onDelete: Cascade) // Snapshot data stored as JSONB snapshotJson Json - truncated Boolean @default(false) + truncated Boolean @default(false) + + // Analysis created from this snapshot + analysis Analysis? // Traces that reference this snapshot - traces Trace[] + traces Trace[] @@index([gitSessionId]) @@index([createdAt]) } -model Plan { - id String @id @default(cuid()) - createdAt DateTime @default(now()) +// Analysis result from SpoonOS agent +model Analysis { + id String @id @default(cuid()) + createdAt DateTime @default(now()) gitSessionId String gitSession GitSession @relation(fields: [gitSessionId], references: [id], onDelete: Cascade) - issueType String? - risk String? + // Link to source snapshot + snapshotId String @unique + snapshot Snapshot @relation(fields: [snapshotId], references: [id], onDelete: Cascade) - // Plan data stored as JSONB - planJson Json + // Issue classification + issueType String // merge_conflict, detached_head, rebase_in_progress, clean, unknown + summary String? @db.Text - dangerousAllowed Boolean @default(false) + // Repo graph for visualization (nodes/edges) + repoGraphJson Json? + + // Relations + conflictFiles ConflictFile[] + planSteps PlanStep[] @@index([gitSessionId]) - @@index([createdAt]) + @@index([issueType]) +} + +// Conflict file in a merge conflict +model ConflictFile { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + + analysisId String + analysis Analysis @relation(fields: [analysisId], references: [id], onDelete: Cascade) + + path String + highLevelSummary String? @db.Text + + // Hunks within this file + hunks ConflictHunk[] + + @@index([analysisId]) } +// Individual conflict hunk within a file +model ConflictHunk { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + + conflictFileId String + conflictFile ConflictFile @relation(fields: [conflictFileId], references: [id], onDelete: Cascade) + + index Int // Order within the file + startLine Int? + endLine Int? + + // Three-way content + baseText String @db.Text + oursText String @db.Text + theirsText String @db.Text + + // AI analysis + explanation String? @db.Text + suggestedChoice String? // ours, theirs, manual, combine + suggestedContent String? @db.Text + + // User's choice (for tracking resolution) + userChoice String? // ours, theirs, manual + userContent String? @db.Text + + @@index([conflictFileId]) +} + +// Step in the recovery plan +model PlanStep { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + + analysisId String + analysis Analysis @relation(fields: [analysisId], references: [id], onDelete: Cascade) + + index Int + title String + rationale String? @db.Text + dangerLevel String @default("safe") // safe, caution, dangerous + + // Commands as JSON arrays + commandsJson Json // string[] + verifyJson Json // string[] - commands to verify success + undoJson Json // string[] - commands to undo + + // Execution tracking + status String @default("pending") // pending, completed, skipped + completedAt DateTime? + userConfirmed Boolean @default(false) // For dangerous steps + + @@index([analysisId]) + @@index([index]) +} + +// Trace for SpoonOS pipeline debugging model Trace { - id String @id @default(cuid()) - createdAt DateTime @default(now()) + id String @id @default(cuid()) + createdAt DateTime @default(now()) gitSessionId String gitSession GitSession @relation(fields: [gitSessionId], references: [id], onDelete: Cascade) // SpoonOS stage identifier - stage String // collector, classifier, visual_explainer, planner, verifier + stage String // ingest, analyze, explain, plan, verify // Reference to input snapshot (optional) - snapshotId String? - snapshot Snapshot? @relation(fields: [snapshotId], references: [id], onDelete: SetNull) + snapshotId String? + snapshot Snapshot? @relation(fields: [snapshotId], references: [id], onDelete: SetNull) - // Output data stored as JSONB - outputJson Json + // Input/Output data stored as JSONB + inputJson Json? + outputJson Json? // Execution metadata - durationMs Int? - success Boolean @default(true) - errorMessage String? + durationMs Int? + success Boolean @default(true) + errorMessage String? @db.Text @@index([gitSessionId]) @@index([stage]) @@index([createdAt]) } -// Optional: Event log for audit/analytics +// Event log for audit/analytics model Event { - id String @id @default(cuid()) - createdAt DateTime @default(now()) + id String @id @default(cuid()) + createdAt DateTime @default(now()) - type String // session_created, plan_generated, conflict_explained, etc. - userId String? + type String // session_created, analysis_completed, step_executed, etc. + userId String? gitSessionId String? - metadata Json? + metadata Json? @@index([type]) @@index([userId]) diff --git a/apps/web/public/test-snapshots/detached-head.json b/apps/web/public/test-snapshots/detached-head.json new file mode 100644 index 0000000..3d9b7dc --- /dev/null +++ b/apps/web/public/test-snapshots/detached-head.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "timestamp": "2024-12-21T11:00:00.000Z", + "platform": "linux", + "repoRoot": "/home/dev/webapp", + "gitDir": "/home/dev/webapp/.git", + "branch": { + "head": "(detached)", + "oid": "a1b2c3d4e5f6789", + "aheadBehind": { "ahead": 0, "behind": 0 } + }, + "isDetachedHead": true, + "rebaseState": { + "inProgress": false, + "type": "none" + }, + "unmergedFiles": [], + "stagedFiles": ["src/components/Button.tsx", "src/styles/theme.css"], + "modifiedFiles": ["src/App.tsx", "package.json"], + "untrackedFiles": ["temp-debug.log"], + "recentLog": [ + { "hash": "a1b2c3d", "refs": ["HEAD"], "message": "Fix button hover state" }, + { "hash": "e5f6789", "refs": ["tag: v2.0.0"], "message": "Release v2.0.0" }, + { "hash": "b3c4d5e", "refs": [], "message": "Update dependencies" }, + { "hash": "f6g7h8i", "refs": ["origin/main", "main"], "message": "Add dark mode support" }, + { "hash": "j9k0l1m", "refs": [], "message": "Refactor component structure" }, + { "hash": "n2o3p4q", "refs": ["origin/develop"], "message": "WIP: New feature" } + ], + "recentReflog": [ + { "hash": "a1b2c3d", "selector": "HEAD@{0}", "action": "checkout", "message": "checkout: moving from main to a1b2c3d" }, + { "hash": "f6g7h8i", "selector": "HEAD@{1}", "action": "commit", "message": "commit: Add dark mode support" }, + { "hash": "j9k0l1m", "selector": "HEAD@{2}", "action": "commit", "message": "commit: Refactor component structure" }, + { "hash": "n2o3p4q", "selector": "HEAD@{3}", "action": "checkout", "message": "checkout: moving from develop to main" } + ], + "commitGraph": "* a1b2c3d (HEAD) Fix button hover state\n* e5f6789 (tag: v2.0.0) Release v2.0.0\n* b3c4d5e Update dependencies\n* f6g7h8i (origin/main, main) Add dark mode support\n* j9k0l1m Refactor component structure", + "rawStatus": "M src/components/Button.tsx\nM src/styles/theme.css\n M src/App.tsx\n M package.json\n?? temp-debug.log", + "rawBranches": "* (HEAD detached at a1b2c3d)\n main f6g7h8i Add dark mode support\n develop n2o3p4q WIP: New feature" +} diff --git a/apps/web/public/test-snapshots/merge-conflict.json b/apps/web/public/test-snapshots/merge-conflict.json new file mode 100644 index 0000000..ca5201d --- /dev/null +++ b/apps/web/public/test-snapshots/merge-conflict.json @@ -0,0 +1,68 @@ +{ + "version": 1, + "timestamp": "2024-12-21T10:30:00.000Z", + "platform": "darwin", + "repoRoot": "/Users/dev/my-project", + "gitDir": "/Users/dev/my-project/.git", + "branch": { + "head": "feature/user-auth", + "oid": "abc123def456789", + "upstream": "origin/feature/user-auth", + "aheadBehind": { "ahead": 2, "behind": 0 } + }, + "isDetachedHead": false, + "rebaseState": { + "inProgress": false, + "type": "none" + }, + "unmergedFiles": [ + { + "path": "src/auth/login.ts", + "stageOurs": "100644", + "stageTheirs": "100644", + "conflictBlocks": [ + { + "startLine": 15, + "endLine": 28, + "oursContent": "export async function login(email: string, password: string) {\n const user = await db.users.findByEmail(email);\n if (!user) throw new AuthError('User not found');\n const valid = await bcrypt.compare(password, user.password);\n if (!valid) throw new AuthError('Invalid password');\n return createSession(user);\n}", + "theirsContent": "export async function login(credentials: LoginCredentials) {\n const { email, password } = credentials;\n const user = await userRepository.findByEmail(email);\n if (!user) throw new UnauthorizedError('Invalid credentials');\n const isValid = await verifyPassword(password, user.passwordHash);\n if (!isValid) throw new UnauthorizedError('Invalid credentials');\n return sessionService.create(user.id);\n}", + "context": "import { db } from '../database';\nimport { createSession } from './session';\n\n// Authentication logic" + } + ] + }, + { + "path": "src/config/database.ts", + "stageOurs": "100644", + "stageTheirs": "100644", + "conflictBlocks": [ + { + "startLine": 5, + "endLine": 12, + "oursContent": "export const dbConfig = {\n host: 'localhost',\n port: 5432,\n database: 'myapp_dev'\n};", + "theirsContent": "export const dbConfig = {\n host: process.env.DB_HOST || 'localhost',\n port: parseInt(process.env.DB_PORT || '5432'),\n database: process.env.DB_NAME || 'myapp'\n};", + "context": "// Database configuration\n\nimport { PoolConfig } from 'pg';" + } + ] + } + ], + "stagedFiles": ["src/utils/helpers.ts"], + "modifiedFiles": ["README.md"], + "untrackedFiles": ["notes.txt"], + "recentLog": [ + { "hash": "abc123d", "refs": ["HEAD", "feature/user-auth"], "message": "Add user authentication" }, + { "hash": "def456e", "refs": [], "message": "Update login form validation" }, + { "hash": "789ghij", "refs": ["origin/main", "main"], "message": "Initial project setup" }, + { "hash": "klm012n", "refs": [], "message": "Add database migrations" }, + { "hash": "opq345r", "refs": [], "message": "Configure CI/CD pipeline" } + ], + "recentReflog": [ + { "hash": "abc123d", "selector": "HEAD@{0}", "action": "merge", "message": "merge main: Merge made by recursive" }, + { "hash": "def456e", "selector": "HEAD@{1}", "action": "commit", "message": "commit: Update login form validation" }, + { "hash": "789ghij", "selector": "HEAD@{2}", "action": "checkout", "message": "checkout: moving from main to feature/user-auth" } + ], + "commitGraph": "* abc123d (HEAD -> feature/user-auth) Add user authentication\n* def456e Update login form validation\n| * 789ghij (origin/main, main) Update config\n|/\n* klm012n Initial setup", + "mergeHead": "789ghij", + "mergeMessage": "Merge branch 'main' into feature/user-auth", + "rawStatus": "UU src/auth/login.ts\nUU src/config/database.ts\nM src/utils/helpers.ts\n M README.md\n?? notes.txt", + "rawBranches": "* feature/user-auth abc123d Add user authentication\n main 789ghij Update config" +} diff --git a/apps/web/public/test-snapshots/rebase-in-progress.json b/apps/web/public/test-snapshots/rebase-in-progress.json new file mode 100644 index 0000000..0a013bf --- /dev/null +++ b/apps/web/public/test-snapshots/rebase-in-progress.json @@ -0,0 +1,57 @@ +{ + "version": 1, + "timestamp": "2024-12-21T11:30:00.000Z", + "platform": "win32", + "repoRoot": "C:/Projects/api-server", + "gitDir": "C:/Projects/api-server/.git", + "branch": { + "head": "(no branch, rebasing feature/api-v2)", + "oid": "x1y2z3w4v5u6789", + "upstream": "origin/feature/api-v2", + "aheadBehind": { "ahead": 3, "behind": 5 } + }, + "isDetachedHead": true, + "rebaseState": { + "inProgress": true, + "type": "merge", + "headName": "feature/api-v2", + "onto": "main", + "currentStep": 2, + "totalSteps": 4 + }, + "unmergedFiles": [ + { + "path": "src/routes/users.ts", + "stageOurs": "100644", + "stageTheirs": "100644", + "conflictBlocks": [ + { + "startLine": 22, + "endLine": 35, + "oursContent": "router.get('/users/:id', async (req, res) => {\n const user = await UserService.getById(req.params.id);\n if (!user) return res.status(404).json({ error: 'Not found' });\n res.json(user);\n});", + "theirsContent": "router.get('/users/:id', validateParams(userIdSchema), async (req, res) => {\n const user = await userRepository.findById(req.validatedParams.id);\n if (!user) throw new NotFoundError('User not found');\n res.json(userSerializer.serialize(user));\n});", + "context": "import { Router } from 'express';\nimport { UserService } from '../services/user';\n\nconst router = Router();" + } + ] + } + ], + "stagedFiles": ["src/middleware/validation.ts"], + "modifiedFiles": [], + "untrackedFiles": [], + "recentLog": [ + { "hash": "x1y2z3w", "refs": ["HEAD"], "message": "Apply commit 2/4: Add validation middleware" }, + { "hash": "m4n5o6p", "refs": ["origin/main", "main"], "message": "Update Express to v5" }, + { "hash": "q7r8s9t", "refs": [], "message": "Add rate limiting" }, + { "hash": "u0v1w2x", "refs": [], "message": "Implement caching layer" }, + { "hash": "y3z4a5b", "refs": ["origin/feature/api-v2"], "message": "Refactor API v2 endpoints" } + ], + "recentReflog": [ + { "hash": "x1y2z3w", "selector": "HEAD@{0}", "action": "rebase", "message": "rebase (continue): Add validation middleware" }, + { "hash": "c6d7e8f", "selector": "HEAD@{1}", "action": "rebase", "message": "rebase (pick): Update error handling" }, + { "hash": "m4n5o6p", "selector": "HEAD@{2}", "action": "rebase", "message": "rebase (start): checkout main" }, + { "hash": "y3z4a5b", "selector": "HEAD@{3}", "action": "checkout", "message": "checkout: moving from main to feature/api-v2" } + ], + "commitGraph": "* x1y2z3w (HEAD) Apply commit 2/4: Add validation middleware\n* m4n5o6p (origin/main, main) Update Express to v5\n* q7r8s9t Add rate limiting\n|\n| * y3z4a5b (origin/feature/api-v2) Refactor API v2 endpoints\n|/\n* u0v1w2x Implement caching layer", + "rawStatus": "UU src/routes/users.ts\nM src/middleware/validation.ts", + "rawBranches": "* (no branch, rebasing feature/api-v2)\n main m4n5o6p Update Express to v5\n feature/api-v2 y3z4a5b Refactor API v2 endpoints" +} diff --git a/apps/web/src/app/analyzer/page.tsx b/apps/web/src/app/analyzer/page.tsx deleted file mode 100644 index e7c8f03..0000000 --- a/apps/web/src/app/analyzer/page.tsx +++ /dev/null @@ -1,130 +0,0 @@ -'use client'; - -import React, { useState } from 'react'; -import { ArrowLeft, ChevronDown, ChevronUp } from 'lucide-react'; -import Link from 'next/link'; - -const ConflictAnalyzer = () => { - const [showContext, setShowContext] = useState(false); - - return ( -
-
-
- - - Back to home - - -
-
-

Conflict Analysis

-

src/routes/users.ts • Lines 22-35

-
- -
-
- -
-
- Mixed -
-
- moderate complexity -
-
- -
-
-
-
- OURS - (no branch, rebasing feature/api-v2) -
-
-
-
-                
-{`router.get('/users/:id', async (req, res) => {
-  const user = await UserService.getById(req.params.id);
-  if (!user) return res.status(404).json({ error: 'Not found' });
-});
-  res.json(user);
-});`}
-                
-              
-
-
- -
-
-
- THEIRS - incoming -
-
-
-
-                
-{`router.get('/users/:id', validateParams(userIdSchema), async (req, res) => {
-  const user = await userRepository.findById(req.validatedParams.id);
-  if (!user) throw new NotFoundError('User not found');
-  res.json(userSerializer.serialize(user));
-});`}
-                
-              
-
-
-
- -
-
-

- What OURS changed: -

-

- The current branch's version of the GET /users/:id route handler uses the UserService to fetch the user by ID and returns the user object directly. If the user is not found, it returns a 404 Not Found response. -

-
- -
-

- What THEIRS changed: -

-

- The incoming version of the GET /users/:id route handler uses the userRepository to fetch the user by ID, validates the ID using the userIdSchema, and serializes the user object using the userSerializer before returning it. If the user is not found, it throws a NotFoundError. -

-
- -
-

- Why this conflict occurred: -

-

- The two versions of the route handler have different implementations, with the current branch's version using a UserService and the incoming version using a userRepository and serializer. Additionally, the error handling and response formats differ between the two versions. -

-
- -
-

- Suggested resolution strategy: -

-

- Merge the two versions by keeping the core functionality of fetching the user by ID, but combine the error handling and response formatting. Use the userRepository and serializer from the incoming version, but handle the NotFoundError and return a 404 response like the current branch's version. -

-
-
- -
- - View Resolution Steps - -
-
-
- ); -}; - -export default ConflictAnalyzer; \ No newline at end of file diff --git a/apps/web/src/app/api/auth/register/route.ts b/apps/web/src/app/api/auth/register/route.ts index 0802417..c693e66 100644 --- a/apps/web/src/app/api/auth/register/route.ts +++ b/apps/web/src/app/api/auth/register/route.ts @@ -1,8 +1,12 @@ import { NextRequest, NextResponse } from 'next/server'; import prisma from '@/lib/prisma'; import { hashPassword } from '@/lib/auth'; +import { createAuthLog, extractClientIp } from '@/lib/db'; export async function POST(request: NextRequest) { + const ipAddress = extractClientIp(request.headers); + const userAgent = request.headers.get('user-agent'); + try { const { name, email, password } = await request.json(); @@ -26,6 +30,15 @@ export async function POST(request: NextRequest) { }); if (existingUser) { + await createAuthLog({ + userId: null, + action: 'REGISTER_FAILED', + ipAddress, + userAgent, + success: false, + failureReason: 'Email already exists', + }).catch(console.error); + return NextResponse.json( { error: 'An account with this email already exists' }, { status: 409 } @@ -48,6 +61,15 @@ export async function POST(request: NextRequest) { }, }); + // Log successful registration + await createAuthLog({ + userId: user.id, + action: 'REGISTER', + ipAddress, + userAgent, + success: true, + }).catch(console.error); + return NextResponse.json({ user }, { status: 201 }); } catch (error) { console.error('Registration error:', error); diff --git a/apps/web/src/app/api/incident/[id]/route.ts b/apps/web/src/app/api/incident/[id]/route.ts new file mode 100644 index 0000000..2295930 --- /dev/null +++ b/apps/web/src/app/api/incident/[id]/route.ts @@ -0,0 +1,95 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getSessionWithDetails } from '@/lib/db'; + +/** + * GET /api/incident/[id] + * + * Fetch session data with all related details for the incident room. + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params; + const session = await getSessionWithDetails(id); + + if (!session) { + return NextResponse.json( + { error: 'Session not found' }, + { status: 404 } + ); + } + + // Get the latest snapshot and analysis + const snapshot = session.snapshots[0]; + const analysis = session.analyses[0]; + + // Transform to the expected format + const response = { + id: session.id, + title: session.title, + status: session.status, + createdAt: session.createdAt.toISOString(), + snapshot: snapshot ? { + branch: (snapshot.snapshotJson as { branch?: { head?: string; oid?: string } })?.branch || { head: 'unknown', oid: '' }, + platform: (snapshot.snapshotJson as { platform?: string })?.platform || 'unknown', + isDetachedHead: (snapshot.snapshotJson as { isDetachedHead?: boolean })?.isDetachedHead || false, + rebaseState: (snapshot.snapshotJson as { rebaseState?: { inProgress: boolean } })?.rebaseState || { inProgress: false }, + } : { + branch: { head: 'unknown', oid: '' }, + platform: 'unknown', + isDetachedHead: false, + rebaseState: { inProgress: false }, + }, + analysis: analysis ? { + issueType: analysis.issueType, + summary: analysis.summary, + repoGraphJson: analysis.repoGraphJson, + conflictFiles: analysis.conflictFiles.map(file => ({ + id: file.id, + path: file.path, + highLevelSummary: file.highLevelSummary, + hunks: file.hunks.map(hunk => ({ + id: hunk.id, + index: hunk.index, + baseText: hunk.baseText, + oursText: hunk.oursText, + theirsText: hunk.theirsText, + explanation: hunk.explanation, + suggestedChoice: hunk.suggestedChoice, + userChoice: hunk.userChoice, + })), + })), + planSteps: analysis.planSteps.map(step => ({ + id: step.id, + index: step.index, + title: step.title, + rationale: step.rationale, + commandsJson: step.commandsJson, + verifyJson: step.verifyJson, + undoJson: step.undoJson, + dangerLevel: step.dangerLevel, + status: step.status, + })), + } : null, + traces: session.traces.map(trace => ({ + id: trace.id, + stage: trace.stage, + inputJson: trace.inputJson, + outputJson: trace.outputJson, + durationMs: trace.durationMs, + success: trace.success, + createdAt: trace.createdAt.toISOString(), + })), + }; + + return NextResponse.json(response); + } catch (error) { + console.error('Error fetching session:', error); + return NextResponse.json( + { error: 'Failed to fetch session' }, + { status: 500 } + ); + } +} diff --git a/apps/web/src/app/api/sessions/[id]/plan/route.ts b/apps/web/src/app/api/sessions/[id]/plan/route.ts index 4a9c3a7..39d7ac5 100644 --- a/apps/web/src/app/api/sessions/[id]/plan/route.ts +++ b/apps/web/src/app/api/sessions/[id]/plan/route.ts @@ -1,8 +1,95 @@ import { NextRequest, NextResponse } from 'next/server'; -import { SnapshotV1Schema, type Signals } from '@gitguard/schema'; -import { getLatestSnapshot, getTraces, createPlan, saveTrace } from '@/lib/db'; -import { classifyIssue, generatePlan } from '@/lib/agent'; +import { SnapshotV1Schema } from '@gitguard/schema'; +import { + getSession, + getLatestSnapshot, + getLatestAnalysis, + updatePlanStepStatus, + createAnalysis, + createPlanStep, + saveTrace, + deleteAnalysisBySnapshotId, +} from '@/lib/db'; +import { collectSignals } from '@/lib/agent/collector'; +import { classifyIssue } from '@/lib/agent/classifier'; +import { generatePlan } from '@/lib/agent/planner'; +/** + * GET /api/sessions/[id]/plan + * Get the recovery plan for a session. + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id: sessionId } = await params; + + const analysis = await getLatestAnalysis(sessionId); + if (!analysis) { + return NextResponse.json({ error: 'No analysis found' }, { status: 404 }); + } + + return NextResponse.json({ + issueType: analysis.issueType, + summary: analysis.summary, + planSteps: analysis.planSteps.map((step) => ({ + id: step.id, + index: step.index, + title: step.title, + rationale: step.rationale, + commands: step.commandsJson, + verify: step.verifyJson, + undo: step.undoJson, + dangerLevel: step.dangerLevel, + status: step.status, + })), + }); + } catch (error) { + console.error('Error fetching plan:', error); + const message = error instanceof Error ? error.message : 'Unknown error'; + return NextResponse.json({ error: message }, { status: 500 }); + } +} + +/** + * PATCH /api/sessions/[id]/plan + * Update a plan step status. + */ +export async function PATCH( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + await params; // Validate params exist + const body = await request.json(); + const { stepId, status, userConfirmed } = body; + + if (!stepId || !status) { + return NextResponse.json( + { error: 'stepId and status are required' }, + { status: 400 } + ); + } + + const updatedStep = await updatePlanStepStatus(stepId, status, userConfirmed); + + return NextResponse.json({ + id: updatedStep.id, + status: updatedStep.status, + completedAt: updatedStep.completedAt?.toISOString(), + }); + } catch (error) { + console.error('Error updating plan step:', error); + const message = error instanceof Error ? error.message : 'Unknown error'; + return NextResponse.json({ error: message }, { status: 500 }); + } +} + +/** + * POST /api/sessions/[id]/plan + * Generate a recovery plan for a session. + */ export async function POST( request: NextRequest, { params }: { params: Promise<{ id: string }> } @@ -10,7 +97,20 @@ export async function POST( try { const { id: sessionId } = await params; - // Get snapshot + // Check for optional dangerousAllowed body parameter + let dangerousAllowed = false; + try { + const body = await request.json(); + dangerousAllowed = body.dangerousAllowed ?? false; + } catch { + // No body or invalid JSON is fine, use defaults + } + + const session = await getSession(sessionId); + if (!session) { + return NextResponse.json({ error: 'Session not found' }, { status: 404 }); + } + const snapshotRecord = await getLatestSnapshot(sessionId); if (!snapshotRecord) { return NextResponse.json({ error: 'Snapshot not found' }, { status: 404 }); @@ -18,46 +118,66 @@ export async function POST( const snapshot = SnapshotV1Schema.parse(snapshotRecord.snapshotJson); - // Get signals from collector trace - const traces = await getTraces(sessionId); - const collectorTrace = traces.find((t) => t.stage === 'collector'); - if (!collectorTrace) { - return NextResponse.json({ error: 'No collector trace found' }, { status: 400 }); - } + // Run the SpoonOS pipeline + const pipelineStart = Date.now(); - const signals = collectorTrace.outputJson as Signals; + // Stage 1: Collect signals + const collectorStart = Date.now(); + const signals = collectSignals(snapshot); + await saveTrace(sessionId, 'collector', snapshotRecord.id, { snapshot: 'parsed' }, signals, collectorStart); - // Run classifier + // Stage 2: Classify issue (may use LLM for ambiguous cases) const classifierStart = Date.now(); const classification = await classifyIssue(signals); - await saveTrace(sessionId, 'classifier', null, classification, classifierStart); + await saveTrace(sessionId, 'classifier', snapshotRecord.id, signals, classification, classifierStart); - // Update signals with refined classification - const refinedSignals: Signals = { + // Update signals with classification results + const classifiedSignals = { ...signals, primaryIssue: classification.primaryIssue, secondaryIssues: classification.secondaryIssues, estimatedRisk: classification.estimatedRisk, }; - // Run planner - const dangerousAllowed = false; // Could be passed from request body + // Stage 3: Generate plan const plannerStart = Date.now(); - const plan = await generatePlan(snapshot, refinedSignals, dangerousAllowed); + const plan = await generatePlan(snapshot, classifiedSignals, dangerousAllowed); + await saveTrace(sessionId, 'planner', snapshotRecord.id, classifiedSignals, plan, plannerStart); - // Save plan - await createPlan({ + // Delete existing analysis for this snapshot (allows regeneration) + await deleteAnalysisBySnapshotId(snapshotRecord.id); + + // Save analysis and plan steps to database + const analysis = await createAnalysis({ gitSessionId: sessionId, + snapshotId: snapshotRecord.id, issueType: plan.issueType, - risk: plan.risk, - planJson: plan, - dangerousAllowed, + summary: plan.issueSummary, }); - // Save planner trace - await saveTrace(sessionId, 'planner', null, plan, plannerStart); + // Create plan steps + for (let i = 0; i < plan.steps.length; i++) { + const step = plan.steps[i]; + await createPlanStep({ + analysisId: analysis.id, + index: i, + title: step.title, + rationale: step.description, + commandsJson: step.commands, + verifyJson: { expected: step.expected }, + undoJson: step.undo, + dangerLevel: step.dangerous ? 'dangerous' : 'safe', + }); + } + + const totalDuration = Date.now() - pipelineStart; - return NextResponse.json({ plan }); + return NextResponse.json({ + success: true, + plan, + analysisId: analysis.id, + duration: totalDuration, + }); } catch (error) { console.error('Error generating plan:', error); const message = error instanceof Error ? error.message : 'Unknown error'; diff --git a/apps/web/src/app/api/sessions/[id]/route.ts b/apps/web/src/app/api/sessions/[id]/route.ts index 680a72d..46886f5 100644 --- a/apps/web/src/app/api/sessions/[id]/route.ts +++ b/apps/web/src/app/api/sessions/[id]/route.ts @@ -1,7 +1,12 @@ import { NextRequest, NextResponse } from 'next/server'; -import { SnapshotV1Schema, type PlanV1, type Signals } from '@gitguard/schema'; +import { SnapshotV1Schema } from '@gitguard/schema'; import { getSessionWithDetails } from '@/lib/db'; +import { collectSignals } from '@/lib/agent/collector'; +/** + * GET /api/sessions/[id] + * Get session data with snapshot, analysis, and traces. + */ export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } @@ -20,35 +25,103 @@ export async function GET( } const snapshot = SnapshotV1Schema.parse(latestSnapshot.snapshotJson); + const latestAnalysis = sessionData.analyses[0]; - const latestPlan = sessionData.plans[0]; - const plan: PlanV1 | null = latestPlan - ? (latestPlan.planJson as PlanV1) - : null; + // Compute signals from snapshot + const signals = collectSignals(snapshot); const traces = sessionData.traces.map((t) => ({ stage: t.stage, + input: t.inputJson, output: t.outputJson, createdAt: t.createdAt.toISOString(), durationMs: t.durationMs, + success: t.success, })); - // Get signals from collector trace - let signals: Signals | null = null; - const collectorTrace = sessionData.traces.find((t) => t.stage === 'collector'); - if (collectorTrace) { - signals = collectorTrace.outputJson as Signals; + // Transform plan steps from database to PlanV1 format + let plan = null; + if (latestAnalysis && latestAnalysis.planSteps.length > 0) { + plan = { + version: 1, + timestamp: latestAnalysis.createdAt.toISOString(), + issueType: latestAnalysis.issueType, + issueSummary: latestAnalysis.summary || 'Recovery plan generated', + risk: signals.estimatedRisk || 'medium', + steps: latestAnalysis.planSteps.map((s) => { + // Parse undo from JSON + const undoData = s.undoJson as { commands?: string[]; description?: string; possible?: boolean } | string[] || {}; + const undoCommands = Array.isArray(undoData) ? undoData : (undoData.commands || []); + const undoDescription = Array.isArray(undoData) ? 'Undo this step' : (undoData.description || 'Undo this step'); + + return { + id: s.id, + title: s.title, + description: s.rationale || s.title, + commands: Array.isArray(s.commandsJson) ? s.commandsJson : [], + expected: typeof s.verifyJson === 'object' && s.verifyJson !== null && 'expected' in s.verifyJson + ? String((s.verifyJson as { expected?: unknown }).expected) + : 'Command executed successfully', + undo: { + possible: true, + commands: undoCommands, + description: undoDescription, + }, + dangerous: s.dangerLevel === 'dangerous', + requiresUserInput: false, + }; + }), + reflogRecovery: { + description: 'If something goes wrong, use reflog to recover', + relevantEntries: ['HEAD@{1}'], + recoveryCommand: 'git reset --hard HEAD@{1}', + }, + }; } return NextResponse.json({ session: { id: sessionData.id, title: sessionData.title, + status: sessionData.status, createdAt: sessionData.createdAt.toISOString(), }, snapshot, signals, plan, + analysis: latestAnalysis + ? { + issueType: latestAnalysis.issueType, + summary: latestAnalysis.summary, + repoGraph: latestAnalysis.repoGraphJson, + conflictFiles: latestAnalysis.conflictFiles.map((f) => ({ + id: f.id, + path: f.path, + summary: f.highLevelSummary, + hunks: f.hunks.map((h) => ({ + id: h.id, + index: h.index, + baseText: h.baseText, + oursText: h.oursText, + theirsText: h.theirsText, + explanation: h.explanation, + suggestedChoice: h.suggestedChoice, + userChoice: h.userChoice, + })), + })), + planSteps: latestAnalysis.planSteps.map((s) => ({ + id: s.id, + index: s.index, + title: s.title, + rationale: s.rationale, + commands: s.commandsJson, + verify: s.verifyJson, + undo: s.undoJson, + dangerLevel: s.dangerLevel, + status: s.status, + })), + } + : null, traces, }); } catch (error) { diff --git a/apps/web/src/app/api/sessions/[id]/verify/route.ts b/apps/web/src/app/api/sessions/[id]/verify/route.ts index 4ccd080..987921f 100644 --- a/apps/web/src/app/api/sessions/[id]/verify/route.ts +++ b/apps/web/src/app/api/sessions/[id]/verify/route.ts @@ -1,8 +1,13 @@ import { NextRequest, NextResponse } from 'next/server'; -import { SnapshotV1Schema, type Signals, type PlanV1 } from '@gitguard/schema'; -import { getLatestPlan, getTraces, createSnapshot, saveTrace } from '@/lib/db'; -import { verifyProgress, collectSignals } from '@/lib/agent'; +import { SnapshotV1Schema } from '@gitguard/schema'; +import { createSnapshot, getLatestAnalysis, getTraces, saveTrace } from '@/lib/db'; +const AGENT_URL = process.env.AGENT_URL || 'http://localhost:8000'; + +/** + * POST /api/sessions/[id]/verify + * Upload a new snapshot to verify progress. + */ export async function POST( request: NextRequest, { params }: { params: Promise<{ id: string }> } @@ -21,33 +26,53 @@ export async function POST( snapshotJson: newSnapshot, }); - // Get original signals from collector trace - const traces = await getTraces(sessionId); - const collectorTrace = traces.find((t) => t.stage === 'collector'); - if (!collectorTrace) { - return NextResponse.json({ error: 'No collector trace found' }, { status: 400 }); - } - - const originalSignals = collectorTrace.outputJson as Signals; + // Get original analysis + const originalAnalysis = await getLatestAnalysis(sessionId); + const originalIssueType = originalAnalysis?.issueType || 'unknown'; - // Get current plan - const planRecord = await getLatestPlan(sessionId); - if (!planRecord) { - return NextResponse.json({ error: 'No plan found' }, { status: 400 }); + // Determine new issue type based on snapshot + let newIssueType = 'unknown'; + if (newSnapshot.unmergedFiles.length > 0) { + newIssueType = 'merge_conflict'; + } else if (newSnapshot.rebaseState.inProgress) { + newIssueType = 'rebase_in_progress'; + } else if (newSnapshot.isDetachedHead) { + newIssueType = 'detached_head'; + } else { + newIssueType = 'clean'; } - const plan = planRecord.planJson as PlanV1; + const resolved = newIssueType === 'clean'; + const remainingIssues: string[] = []; - // Run verifier - const verifierStart = Date.now(); - const result = await verifyProgress(newSnapshot, originalSignals, plan); + if (newSnapshot.unmergedFiles.length > 0) { + remainingIssues.push(...newSnapshot.unmergedFiles.map((f) => `Conflict: ${f.path}`)); + } + if (newSnapshot.rebaseState.inProgress) { + remainingIssues.push('Rebase in progress'); + } + if (newSnapshot.isDetachedHead) { + remainingIssues.push('Detached HEAD'); + } - // Save new collector trace for the new snapshot - const newSignals = collectSignals(newSnapshot); - await saveTrace(sessionId, 'collector', snapshotRecord.id, newSignals); + const result = { + previousIssue: originalIssueType, + currentIssue: newIssueType, + resolved, + remainingIssues, + snapshotId: snapshotRecord.id, + }; - // Save verifier trace - await saveTrace(sessionId, 'verifier', snapshotRecord.id, result, verifierStart); + // Save verify trace + await saveTrace( + sessionId, + 'verify', + snapshotRecord.id, + { previousSnapshot: 'omitted', newSnapshot: 'omitted' }, + result, + Date.now(), + true + ); return NextResponse.json(result); } catch (error) { diff --git a/apps/web/src/app/api/sessions/route.ts b/apps/web/src/app/api/sessions/route.ts index 2583b0d..1ff8dc6 100644 --- a/apps/web/src/app/api/sessions/route.ts +++ b/apps/web/src/app/api/sessions/route.ts @@ -1,10 +1,45 @@ import { NextRequest, NextResponse } from 'next/server'; import { SnapshotV1Schema } from '@gitguard/schema'; -import { createSession, createSnapshot, saveTrace } from '@/lib/db'; +import { createSession, createSnapshot, saveTrace, getUserSessions } from '@/lib/db'; import { collectSignals } from '@/lib/agent'; import { auth } from '@/lib/auth'; import { createHash } from 'crypto'; +export async function GET() { + try { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const sessions = await getUserSessions(session.user.id); + + // Transform to match frontend interface + const formattedSessions = sessions.map(s => ({ + id: s.id, + createdAt: s.createdAt.toISOString(), + title: s.title, + status: s.status, + analysis: s.analyses[0] ? { + issueType: s.analyses[0].issueType, + summary: s.analyses[0].summary, + } : null, + traces: s.traces.map(t => ({ + stage: t.stage, + outputJson: t.outputJson, + createdAt: t.createdAt.toISOString(), + success: t.success, + })), + })); + + return NextResponse.json({ sessions: formattedSessions }); + } catch (error) { + console.error('Error fetching sessions:', error); + const message = error instanceof Error ? error.message : 'Unknown error'; + return NextResponse.json({ error: message }, { status: 500 }); + } +} + export async function POST(request: NextRequest) { try { const body = await request.json(); diff --git a/apps/web/src/app/api/snapshots/ingest/route.ts b/apps/web/src/app/api/snapshots/ingest/route.ts new file mode 100644 index 0000000..ad94699 --- /dev/null +++ b/apps/web/src/app/api/snapshots/ingest/route.ts @@ -0,0 +1,385 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { SnapshotV1Schema } from '@gitguard/schema'; +import { createSession, createSnapshot, updateSessionStatus, saveTrace, createAnalysis, createConflictFile, createConflictHunk, createPlanStep } from '@/lib/db'; +import { auth } from '@/lib/auth'; +import { createHash } from 'crypto'; + +const AGENT_URL = process.env.AGENT_URL || 'http://localhost:8000'; + +interface AgentAnalysis { + issueType: string; + summary: string; + repoGraph?: { + nodes: Array<{ id: string; type: string; label: string; sha?: string; isCurrent?: boolean }>; + edges: Array<{ from: string; to: string; type?: string }>; + }; + conflicts?: Array<{ + path: string; + highLevelSummary?: string; + hunks: Array<{ + index: number; + startLine?: number; + endLine?: number; + baseText: string; + oursText: string; + theirsText: string; + explanation?: string; + suggestedChoice?: string; + suggestedContent?: string; + }>; + }>; + plan: Array<{ + index: number; + title: string; + rationale?: string; + commands: string[]; + verify: string[]; + undo: string[]; + dangerLevel: string; + }>; +} + +/** + * POST /api/snapshots/ingest + * + * Ingest a snapshot from CLI, analyze with SpoonOS agent, and return session URL. + * This is the main entry point for the CLI `gitguard send` command. + */ +export async function POST(request: NextRequest) { + const startTime = Date.now(); + + try { + const body = await request.json(); + const { snapshot: rawSnapshot } = body; + + // Validate snapshot + const snapshot = SnapshotV1Schema.parse(rawSnapshot); + + // Get current user (optional - uploads can be anonymous) + const session = await auth(); + const userId = session?.user?.id || null; + + // Create hash of repo root for deduplication + const repoRootHash = createHash('sha256') + .update(snapshot.repoRoot) + .digest('hex') + .slice(0, 16); + + // Generate title from branch and issue + const title = generateTitle(snapshot); + + // Create session in database + const gitSession = await createSession({ + title, + os: snapshot.platform, + repoRootHash, + userId, + status: 'analyzing', + }); + + // Save snapshot + const snapshotRecord = await createSnapshot({ + gitSessionId: gitSession.id, + snapshotJson: snapshot, + }); + + // Save ingest trace + await saveTrace( + gitSession.id, + 'ingest', + snapshotRecord.id, + { source: 'api' }, + { snapshotId: snapshotRecord.id }, + startTime + ); + + // Call SpoonOS agent for analysis + const analyzeStartTime = Date.now(); + let agentResponse: { success: boolean; analysis?: AgentAnalysis; error?: string }; + + try { + const agentResult = await fetch(`${AGENT_URL}/analyze`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + snapshot, + options: { + includeGraph: true, + maxConflictFiles: 10, + maxHunksPerFile: 5, + }, + }), + }); + + if (!agentResult.ok) { + throw new Error(`Agent returned ${agentResult.status}`); + } + + agentResponse = await agentResult.json(); + } catch (agentError) { + // Fallback to basic analysis if agent is unavailable + console.error('Agent unavailable, using fallback analysis:', agentError); + agentResponse = generateFallbackAnalysis(snapshot); + } + + // Save analyze trace + await saveTrace( + gitSession.id, + 'analyze', + snapshotRecord.id, + { snapshotId: snapshotRecord.id }, + agentResponse, + analyzeStartTime, + agentResponse.success + ); + + if (!agentResponse.success || !agentResponse.analysis) { + await updateSessionStatus(gitSession.id, 'error'); + return NextResponse.json( + { error: agentResponse.error || 'Analysis failed' }, + { status: 500 } + ); + } + + const analysis = agentResponse.analysis; + + // Store analysis in database + const analysisRecord = await createAnalysis({ + gitSessionId: gitSession.id, + snapshotId: snapshotRecord.id, + issueType: analysis.issueType, + summary: analysis.summary, + repoGraphJson: analysis.repoGraph, + }); + + // Store conflict files and hunks + if (analysis.conflicts) { + for (const conflict of analysis.conflicts) { + const conflictFile = await createConflictFile({ + analysisId: analysisRecord.id, + path: conflict.path, + highLevelSummary: conflict.highLevelSummary, + }); + + for (const hunk of conflict.hunks) { + await createConflictHunk({ + conflictFileId: conflictFile.id, + index: hunk.index, + startLine: hunk.startLine, + endLine: hunk.endLine, + baseText: hunk.baseText, + oursText: hunk.oursText, + theirsText: hunk.theirsText, + explanation: hunk.explanation, + suggestedChoice: hunk.suggestedChoice, + suggestedContent: hunk.suggestedContent, + }); + } + } + } + + // Store plan steps + for (const step of analysis.plan) { + await createPlanStep({ + analysisId: analysisRecord.id, + index: step.index, + title: step.title, + rationale: step.rationale, + commandsJson: step.commands, + verifyJson: step.verify, + undoJson: step.undo, + dangerLevel: step.dangerLevel, + }); + } + + // Update session status to ready + await updateSessionStatus(gitSession.id, 'ready'); + + // Build response URL + const baseUrl = process.env.NEXTAUTH_URL || process.env.AUTH_URL || 'http://localhost:3000'; + const sessionUrl = `${baseUrl}/incident/${gitSession.id}`; + + return NextResponse.json({ + sessionId: gitSession.id, + url: sessionUrl, + analysis: { + issueType: analysis.issueType, + summary: analysis.summary, + }, + }); + } catch (error) { + console.error('Error ingesting snapshot:', error); + const message = error instanceof Error ? error.message : 'Unknown error'; + return NextResponse.json({ error: message }, { status: 400 }); + } +} + +function generateTitle(snapshot: { + branch: { head: string }; + isDetachedHead: boolean; + unmergedFiles: unknown[]; + rebaseState: { inProgress: boolean }; +}): string { + const parts: string[] = []; + + if (snapshot.unmergedFiles.length > 0) { + parts.push('Merge Conflict'); + } else if (snapshot.rebaseState.inProgress) { + parts.push('Rebase'); + } else if (snapshot.isDetachedHead) { + parts.push('Detached HEAD'); + } + + parts.push(`on ${snapshot.branch.head}`); + + return parts.join(' ') || 'Git Recovery Session'; +} + +function generateFallbackAnalysis(snapshot: { + unmergedFiles: Array<{ path: string; conflictBlocks?: Array<{ oursContent: string; theirsContent: string; context?: string }> }>; + isDetachedHead: boolean; + rebaseState: { inProgress: boolean }; + branch: { head: string }; +}): { success: boolean; analysis: AgentAnalysis } { + let issueType = 'unknown'; + let summary = 'Repository state analysis'; + + if (snapshot.unmergedFiles.length > 0) { + issueType = 'merge_conflict'; + summary = `Found ${snapshot.unmergedFiles.length} file(s) with merge conflicts. Review each conflict and choose how to resolve.`; + } else if (snapshot.rebaseState.inProgress) { + issueType = 'rebase_in_progress'; + summary = 'A rebase operation is in progress. You can continue, skip, or abort.'; + } else if (snapshot.isDetachedHead) { + issueType = 'detached_head'; + summary = 'HEAD is detached. Consider creating a branch to save your work.'; + } else { + issueType = 'clean'; + summary = 'Repository appears to be in a clean state.'; + } + + // Extract conflicts + const conflicts = snapshot.unmergedFiles.map((file) => ({ + path: file.path, + hunks: (file.conflictBlocks || []).map((block, i) => ({ + index: i, + baseText: block.context || '', + oursText: block.oursContent || '', + theirsText: block.theirsContent || '', + })), + })); + + // Generate basic plan + const plan: AgentAnalysis['plan'] = []; + + if (issueType === 'merge_conflict') { + plan.push( + { + index: 0, + title: 'Review conflicts', + rationale: 'Understand what changes conflict before resolving', + commands: ['git status', 'git diff --name-only --diff-filter=U'], + verify: ['git status'], + undo: [], + dangerLevel: 'safe', + }, + { + index: 1, + title: 'Resolve each conflict', + rationale: 'Edit files to remove conflict markers and choose correct content', + commands: ['# Edit files manually or use the Conflict Explorer'], + verify: ['git diff '], + undo: ['git checkout --conflict=merge '], + dangerLevel: 'safe', + }, + { + index: 2, + title: 'Stage resolved files', + rationale: 'Mark conflicts as resolved', + commands: ['git add '], + verify: ['git status'], + undo: ['git reset HEAD '], + dangerLevel: 'safe', + }, + { + index: 3, + title: 'Complete merge', + rationale: 'Commit the merge', + commands: ['git commit'], + verify: ['git log -1'], + undo: ['git reset --soft HEAD~1'], + dangerLevel: 'caution', + } + ); + } else if (issueType === 'detached_head') { + plan.push( + { + index: 0, + title: 'Check current state', + rationale: 'Understand where HEAD is pointing', + commands: ['git log --oneline -5', 'git status'], + verify: [], + undo: [], + dangerLevel: 'safe', + }, + { + index: 1, + title: 'Create branch to save work', + rationale: 'Preserve commits before switching', + commands: ['git branch temp-save'], + verify: ['git branch'], + undo: ['git branch -d temp-save'], + dangerLevel: 'safe', + }, + { + index: 2, + title: 'Return to main branch', + rationale: 'Switch back to your working branch', + commands: [`git checkout ${snapshot.branch.head}`], + verify: ['git status'], + undo: [], + dangerLevel: 'safe', + } + ); + } else if (issueType === 'rebase_in_progress') { + plan.push( + { + index: 0, + title: 'Check rebase status', + rationale: 'Understand the current rebase state', + commands: ['git status'], + verify: [], + undo: [], + dangerLevel: 'safe', + }, + { + index: 1, + title: 'Option A: Continue rebase', + rationale: 'If conflicts are resolved, continue', + commands: ['git add .', 'git rebase --continue'], + verify: ['git status'], + undo: ['git rebase --abort'], + dangerLevel: 'caution', + }, + { + index: 2, + title: 'Option B: Abort rebase', + rationale: 'Cancel and return to previous state', + commands: ['git rebase --abort'], + verify: ['git log -3'], + undo: [], + dangerLevel: 'safe', + } + ); + } + + return { + success: true, + analysis: { + issueType, + summary, + conflicts: conflicts.length > 0 ? conflicts : undefined, + plan, + }, + }; +} diff --git a/apps/web/src/app/auth/signin/page.tsx b/apps/web/src/app/auth/signin/page.tsx index 7f764c9..6d8881d 100644 --- a/apps/web/src/app/auth/signin/page.tsx +++ b/apps/web/src/app/auth/signin/page.tsx @@ -8,7 +8,7 @@ import Link from 'next/link'; function SignInForm() { const router = useRouter(); const searchParams = useSearchParams(); - const callbackUrl = searchParams.get('callbackUrl') || '/'; + const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'; const error = searchParams.get('error'); const [email, setEmail] = useState(''); @@ -46,10 +46,19 @@ function SignInForm() { signIn('google', { callbackUrl }); }; + const handleKakaoSignIn = () => { + signIn('kakao', { callbackUrl }); + }; + return (
-
-

Sign In to GitGuard

+
+ + + GitGuard + + +

Sign in to your account

{errorMessage && (
@@ -89,7 +98,7 @@ function SignInForm() { @@ -97,34 +106,49 @@ function SignInForm() {
- or + or continue with
- +
+ + + +

Don't have an account?{' '} @@ -140,9 +164,10 @@ function SignInForm() { function LoadingFallback() { return (

-
+
-
+
+
@@ -154,7 +179,7 @@ function LoadingFallback() { export default function SignInPage() { return ( -
+
}> diff --git a/apps/web/src/app/auth/signup/page.tsx b/apps/web/src/app/auth/signup/page.tsx index a7d5d5d..7212443 100644 --- a/apps/web/src/app/auth/signup/page.tsx +++ b/apps/web/src/app/auth/signup/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState } from 'react'; +import { signIn } from 'next-auth/react'; import { useRouter } from 'next/navigation'; import Link from 'next/link'; @@ -42,7 +43,18 @@ export default function SignUpPage() { if (!response.ok) { setErrorMessage(data.error || 'Failed to create account'); } else { - router.push('/auth/signin?registered=true'); + // Auto sign-in after registration + const result = await signIn('credentials', { + email, + password, + redirect: false, + }); + + if (result?.ok) { + router.push('/dashboard'); + } else { + router.push('/auth/signin?registered=true'); + } } } catch { setErrorMessage('An error occurred. Please try again.'); @@ -51,11 +63,24 @@ export default function SignUpPage() { } }; + const handleGoogleSignIn = () => { + signIn('google', { callbackUrl: '/dashboard' }); + }; + + const handleKakaoSignIn = () => { + signIn('kakao', { callbackUrl: '/dashboard' }); + }; + return ( -
+
-
-

Create Account

+
+ + + GitGuard + + +

Create your account

{errorMessage && (
@@ -63,6 +88,53 @@ export default function SignUpPage() {
)} + {/* OAuth Buttons First */} +
+ + + +
+ +
+
+ or with email +
+
+