KevanSoon commited on
Commit
ec4d503
·
1 Parent(s): 52e8df6

Added Database connection

Browse files
README.md CHANGED
@@ -1,4 +1,104 @@
1
- ---
2
- sdk: docker
3
- app_port: 7860
4
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CS102 Smart Attendance Project
2
+
3
+ A backend Spring Boot application for a smart attendance tracking system.
4
+
5
+ ## ⚙️ Configuration
6
+
7
+ ### Environment Variables
8
+
9
+ Create a `.env` file in the project root or set the following environment variables:
10
+
11
+ ```env
12
+ # Database Configuration
13
+ SPRING_DATASOURCE_URL=your_url
14
+ SPRING_DATASOURCE_USERNAME=your_username
15
+ SPRING_DATASOURCE_PASSWORD=your_password
16
+
17
+
18
+ ## 🚀 Running the Application
19
+
20
+ ### Using Maven Wrapper (Recommended)
21
+
22
+ ```bash
23
+ # On Unix/Linux/macOS
24
+ ./mvnw spring-boot:run
25
+
26
+ # On Windows
27
+ mvnw.cmd spring-boot:run
28
+ ```
29
+
30
+ ### Using Maven
31
+
32
+ ```bash
33
+ # Clean and install dependencies
34
+ mvn clean install
35
+
36
+ # Run the application
37
+ mvn spring-boot:run
38
+ ```
39
+
40
+ ### Using JAR file
41
+
42
+ ```bash
43
+ # Build the JAR file
44
+ mvn clean package
45
+
46
+ # Run the JAR file
47
+ java -jar target/attendance-0.0.1-SNAPSHOT.jar
48
+ ```
49
+
50
+ The application will start on `http://localhost:8080` by default.
51
+
52
+ ### Accessing the Application
53
+
54
+ When you run the application locally, you'll be redirected to a login page due to Spring Security being enabled.
55
+
56
+ **Login Credentials:**
57
+ - **Username:** `user`
58
+ - **Password:** A new password is generated every time you run the project (check your terminal/console output)
59
+
60
+ The generated password will appear in the logs like this:
61
+ ```
62
+ Using generated security password: a1b2c3d4-e5f6-7890-abcd-ef1234567890
63
+ ```
64
+
65
+ Copy this password from your terminal to log into the application.
66
+
67
+ ## 🔗 Application Monitoring
68
+
69
+ The application includes Spring Boot Actuator for basic monitoring:
70
+
71
+ - **Application Health**: `GET /actuator/health`
72
+ - **Application Info**: `GET /actuator/info`
73
+ - **Metrics**: `GET /actuator/metrics`
74
+ - **Prometheus Metrics**: `GET /actuator/prometheus`
75
+
76
+ ## 🧪 Testing
77
+
78
+ ### Run Unit Tests
79
+
80
+ ```bash
81
+ # Using Maven Wrapper
82
+ ./mvnw test
83
+
84
+ # Using Maven
85
+ mvn test
86
+ ```
87
+
88
+ ### Run Integration Tests
89
+
90
+ ```bash
91
+ # Using Maven Wrapper
92
+ ./mvnw verify
93
+
94
+ # Using Maven
95
+ mvn verify
96
+ ```
97
+
98
+ ## 🤝 Contributing
99
+
100
+ 1. Fork the repository
101
+ 2. Create a feature branch: `git checkout -b feature/amazing-feature`
102
+ 3. Commit your changes: `git commit -m 'Add some amazing feature'`
103
+ 4. Push to the branch: `git push origin feature/amazing-feature`
104
+ 5. Open a Pull Request
pom.xml CHANGED
@@ -67,6 +67,11 @@
67
  <artifactId>postgresql</artifactId>
68
  <scope>runtime</scope>
69
  </dependency>
 
 
 
 
 
70
  </dependencies>
71
 
72
  <build>
 
67
  <artifactId>postgresql</artifactId>
68
  <scope>runtime</scope>
69
  </dependency>
70
+ <dependency>
71
+ <groupId>me.paulschwarz</groupId>
72
+ <artifactId>spring-dotenv</artifactId>
73
+ <version>4.0.0</version>
74
+ </dependency>
75
  </dependencies>
76
 
77
  <build>
src/main/java/com/cs102/attendance/TestController.java DELETED
@@ -1,13 +0,0 @@
1
- package com.cs102.attendance;
2
-
3
- import org.springframework.web.bind.annotation.GetMapping;
4
- import org.springframework.web.bind.annotation.RestController;
5
-
6
- @RestController
7
- public class TestController {
8
-
9
- @GetMapping("/hello")
10
- public String hello() {
11
- return "Spring Boot is working!";
12
- }
13
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/main/java/com/cs102/attendance/controller/HealthController.java ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.cs102.attendance.controller;
2
+
3
+ import org.springframework.beans.factory.annotation.Autowired;
4
+ import org.springframework.http.ResponseEntity;
5
+ import org.springframework.web.bind.annotation.GetMapping;
6
+ import org.springframework.web.bind.annotation.RequestMapping;
7
+ import org.springframework.web.bind.annotation.RestController;
8
+
9
+ import javax.sql.DataSource;
10
+ import java.sql.Connection;
11
+ import java.sql.SQLException;
12
+ import java.util.HashMap;
13
+ import java.util.Map;
14
+
15
+ @RestController
16
+ @RequestMapping("/api/health")
17
+ public class HealthController {
18
+
19
+ @Autowired
20
+ private DataSource dataSource;
21
+
22
+ @GetMapping("/database")
23
+ public ResponseEntity<Map<String, Object>> checkDatabaseConnection() {
24
+ Map<String, Object> response = new HashMap<>();
25
+
26
+ try (Connection connection = dataSource.getConnection()) {
27
+ // Test the connection
28
+ boolean isValid = connection.isValid(5); // 5 second timeout
29
+
30
+ if (isValid) {
31
+ response.put("status", "UP");
32
+ response.put("database", "Connected");
33
+ response.put("url", connection.getMetaData().getURL());
34
+ response.put("driver", connection.getMetaData().getDriverName());
35
+ response.put("version", connection.getMetaData().getDatabaseProductVersion());
36
+ return ResponseEntity.ok(response);
37
+ } else {
38
+ response.put("status", "DOWN");
39
+ response.put("database", "Connection invalid");
40
+ return ResponseEntity.status(503).body(response);
41
+ }
42
+ } catch (SQLException e) {
43
+ response.put("status", "DOWN");
44
+ response.put("database", "Connection failed");
45
+ response.put("error", e.getMessage());
46
+ return ResponseEntity.status(503).body(response);
47
+ }
48
+ }
49
+ }
src/main/java/com/cs102/attendance/controller/TestController.java ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.cs102.attendance.controller;
2
+
3
+ import com.cs102.attendance.entity.TestConnection;
4
+ import com.cs102.attendance.repository.TestConnectionRepository;
5
+ import org.springframework.beans.factory.annotation.Autowired;
6
+ import org.springframework.http.ResponseEntity;
7
+ import org.springframework.web.bind.annotation.*;
8
+
9
+ import java.util.HashMap;
10
+ import java.util.List;
11
+ import java.util.Map;
12
+
13
+ @RestController
14
+ @RequestMapping("/api/test")
15
+ public class TestController {
16
+
17
+ @Autowired
18
+ private TestConnectionRepository testConnectionRepository;
19
+
20
+ @GetMapping("/database-operations")
21
+ public ResponseEntity<Map<String, Object>> testDatabaseOperations() {
22
+ Map<String, Object> response = new HashMap<>();
23
+
24
+ try {
25
+ // Test 1: Simple query
26
+ Integer queryResult = testConnectionRepository.testQuery();
27
+ response.put("simpleQuery", queryResult == 1 ? "PASS" : "FAIL");
28
+
29
+ // Test 2: Insert operation
30
+ TestConnection testEntity = new TestConnection("Connection test from Spring Boot");
31
+ TestConnection saved = testConnectionRepository.save(testEntity);
32
+ response.put("insertOperation", saved.getId() != null ? "PASS" : "FAIL");
33
+
34
+ // Test 3: Read operation
35
+ List<TestConnection> allRecords = testConnectionRepository.findAll();
36
+ response.put("readOperation", !allRecords.isEmpty() ? "PASS" : "FAIL");
37
+ response.put("recordCount", allRecords.size());
38
+
39
+ // Test 4: Delete operation
40
+ testConnectionRepository.deleteById(saved.getId());
41
+ response.put("deleteOperation", "PASS");
42
+
43
+ response.put("overallStatus", "SUCCESS");
44
+ response.put("message", "All database operations completed successfully");
45
+
46
+ return ResponseEntity.ok(response);
47
+ } catch (Exception e) {
48
+ response.put("overallStatus", "FAILED");
49
+ response.put("error", e.getMessage());
50
+ response.put("errorClass", e.getClass().getSimpleName());
51
+ return ResponseEntity.status(500).body(response);
52
+ }
53
+ }
54
+
55
+ @PostMapping("/insert-test-data")
56
+ public ResponseEntity<TestConnection> insertTestData(@RequestParam(defaultValue = "Test message") String message) {
57
+ try {
58
+ TestConnection testEntity = new TestConnection(message);
59
+ TestConnection saved = testConnectionRepository.save(testEntity);
60
+ return ResponseEntity.ok(saved);
61
+ } catch (Exception e) {
62
+ return ResponseEntity.status(500).build();
63
+ }
64
+ }
65
+
66
+ @GetMapping("/all-test-data")
67
+ public ResponseEntity<List<TestConnection>> getAllTestData() {
68
+ try {
69
+ List<TestConnection> allRecords = testConnectionRepository.findAll();
70
+ return ResponseEntity.ok(allRecords);
71
+ } catch (Exception e) {
72
+ return ResponseEntity.status(500).build();
73
+ }
74
+ }
75
+ }
src/main/java/com/cs102/attendance/entity/TestConnection.java ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.cs102.attendance.entity;
2
+
3
+ import jakarta.persistence.*;
4
+ import java.time.LocalDateTime;
5
+
6
+ @Entity
7
+ @Table(name = "test_connections")
8
+ public class TestConnection {
9
+
10
+ @Id
11
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
12
+ private Long id;
13
+
14
+ @Column(name = "test_message")
15
+ private String testMessage;
16
+
17
+ @Column(name = "created_at")
18
+ private LocalDateTime createdAt;
19
+
20
+ public TestConnection() {
21
+ this.createdAt = LocalDateTime.now();
22
+ }
23
+
24
+ public TestConnection(String testMessage) {
25
+ this.testMessage = testMessage;
26
+ this.createdAt = LocalDateTime.now();
27
+ }
28
+
29
+ // Getters and setters
30
+ public Long getId() {
31
+ return id;
32
+ }
33
+
34
+ public void setId(Long id) {
35
+ this.id = id;
36
+ }
37
+
38
+ public String getTestMessage() {
39
+ return testMessage;
40
+ }
41
+
42
+ public void setTestMessage(String testMessage) {
43
+ this.testMessage = testMessage;
44
+ }
45
+
46
+ public LocalDateTime getCreatedAt() {
47
+ return createdAt;
48
+ }
49
+
50
+ public void setCreatedAt(LocalDateTime createdAt) {
51
+ this.createdAt = createdAt;
52
+ }
53
+ }
src/main/java/com/cs102/attendance/repository/TestConnectionRepository.java ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.cs102.attendance.repository;
2
+
3
+ import com.cs102.attendance.entity.TestConnection;
4
+ import org.springframework.data.jpa.repository.JpaRepository;
5
+ import org.springframework.data.jpa.repository.Query;
6
+ import org.springframework.stereotype.Repository;
7
+
8
+ @Repository
9
+ public interface TestConnectionRepository extends JpaRepository<TestConnection, Long> {
10
+
11
+ @Query(value = "SELECT 1", nativeQuery = true)
12
+ Integer testQuery();
13
+ }
src/main/resources/application.yml CHANGED
@@ -7,6 +7,10 @@ spring:
7
  username: ${SPRING_DATASOURCE_USERNAME}
8
  password: ${SPRING_DATASOURCE_PASSWORD}
9
  driver-class-name: org.postgresql.Driver
 
 
 
 
10
  jpa:
11
  hibernate:
12
  ddl-auto: update
@@ -14,16 +18,17 @@ spring:
14
  properties:
15
  hibernate:
16
  dialect: org.hibernate.dialect.PostgreSQLDialect
 
 
 
 
17
 
18
- management:
19
- endpoints:
20
- web:
21
- exposure:
22
- include: health,info,metrics,prometheus
23
 
 
24
  recognition:
25
  confidence: 0.6
26
  cooldownMs: 10000
27
-
28
  face-service:
29
- url: http://localhost:5001
 
7
  username: ${SPRING_DATASOURCE_USERNAME}
8
  password: ${SPRING_DATASOURCE_PASSWORD}
9
  driver-class-name: org.postgresql.Driver
10
+ hikari:
11
+ maximum-pool-size: 10
12
+ connection-timeout: 30000
13
+ idle-timeout: 600000
14
  jpa:
15
  hibernate:
16
  ddl-auto: update
 
18
  properties:
19
  hibernate:
20
  dialect: org.hibernate.dialect.PostgreSQLDialect
21
+ # security:
22
+ # user:
23
+ # name: ${SPRING_SECURITY_USER_NAME}
24
+ # password: ${SPRING_SECURITY_USER_PASSWORD}
25
 
26
+ # Expose metrics for demo proof
27
+ management.endpoints.web.exposure.include: health,info,metrics,prometheus
 
 
 
28
 
29
+ # Externalised CV thresholds
30
  recognition:
31
  confidence: 0.6
32
  cooldownMs: 10000
 
33
  face-service:
34
+ url: http://localhost:5001 # backend face service url
src/test/java/com/cs102/attendance/Cs102AttendanceProjectApplicationTests.java CHANGED
@@ -1,13 +1,50 @@
1
  package com.cs102.attendance;
2
 
 
3
  import org.junit.jupiter.api.Test;
 
4
  import org.springframework.boot.test.context.SpringBootTest;
5
 
 
 
 
 
 
 
6
  @SpringBootTest
7
  class Cs102AttendanceProjectApplicationTests {
8
 
 
 
 
 
 
 
9
  @Test
10
  void contextLoads() {
11
  }
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
 
1
  package com.cs102.attendance;
2
 
3
+ import com.cs102.attendance.repository.TestConnectionRepository;
4
  import org.junit.jupiter.api.Test;
5
+ import org.springframework.beans.factory.annotation.Autowired;
6
  import org.springframework.boot.test.context.SpringBootTest;
7
 
8
+ import javax.sql.DataSource;
9
+ import java.sql.Connection;
10
+ import java.sql.SQLException;
11
+
12
+ import static org.junit.jupiter.api.Assertions.*;
13
+
14
  @SpringBootTest
15
  class Cs102AttendanceProjectApplicationTests {
16
 
17
+ @Autowired
18
+ private DataSource dataSource;
19
+
20
+ @Autowired
21
+ private TestConnectionRepository testConnectionRepository;
22
+
23
  @Test
24
  void contextLoads() {
25
  }
26
 
27
+ @Test
28
+ void testDatabaseConnection() throws SQLException {
29
+ // Test that we can get a connection from the DataSource
30
+ try (Connection connection = dataSource.getConnection()) {
31
+ assertNotNull(connection, "Database connection should not be null");
32
+ assertTrue(connection.isValid(5), "Database connection should be valid");
33
+
34
+ // Print connection details for verification
35
+ System.out.println("Database URL: " + connection.getMetaData().getURL());
36
+ System.out.println("Database Driver: " + connection.getMetaData().getDriverName());
37
+ System.out.println("Database Version: " + connection.getMetaData().getDatabaseProductVersion());
38
+ }
39
+ }
40
+
41
+ @Test
42
+ void testRepositoryConnection() {
43
+ // Test that the repository can execute queries
44
+ assertDoesNotThrow(() -> {
45
+ Integer result = testConnectionRepository.testQuery();
46
+ assertEquals(1, result, "Test query should return 1");
47
+ }, "Repository test query should not throw an exception");
48
+ }
49
+
50
  }
src/test/java/com/cs102/attendance/controller/TestControllerTest.java ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package com.cs102.attendance.controller;
2
+
3
+ import com.cs102.attendance.entity.TestConnection;
4
+ import com.cs102.attendance.repository.TestConnectionRepository;
5
+ import com.fasterxml.jackson.databind.ObjectMapper;
6
+ import org.junit.jupiter.api.Test;
7
+ import org.springframework.beans.factory.annotation.Autowired;
8
+ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
9
+ import org.springframework.boot.test.mock.mockito.MockBean;
10
+ import org.springframework.http.MediaType;
11
+ import org.springframework.test.web.servlet.MockMvc;
12
+
13
+ import static org.mockito.ArgumentMatchers.any;
14
+ import static org.mockito.Mockito.when;
15
+ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
16
+ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
17
+
18
+ @WebMvcTest(TestController.class)
19
+ public class TestControllerTest {
20
+
21
+ @Autowired
22
+ private MockMvc mockMvc;
23
+
24
+ @MockBean
25
+ private TestConnectionRepository testConnectionRepository;
26
+
27
+ @Autowired
28
+ private ObjectMapper objectMapper;
29
+
30
+ @Test
31
+ public void testInsertTestDataWithDefaultMessage() throws Exception {
32
+ // Given
33
+ TestConnection mockSavedEntity = new TestConnection("Test message");
34
+ mockSavedEntity.setId(1L);
35
+ when(testConnectionRepository.save(any(TestConnection.class))).thenReturn(mockSavedEntity);
36
+
37
+ // When & Then
38
+ mockMvc.perform(post("/api/test/insert-test-data")
39
+ .contentType(MediaType.APPLICATION_JSON))
40
+ .andExpect(status().isOk())
41
+ .andExpect(jsonPath("$.id").value(1L))
42
+ .andExpect(jsonPath("$.message").value("Test message"));
43
+ }
44
+
45
+ @Test
46
+ public void testInsertTestDataWithCustomMessage() throws Exception {
47
+ // Given
48
+ String customMessage = "Custom test message";
49
+ TestConnection mockSavedEntity = new TestConnection(customMessage);
50
+ mockSavedEntity.setId(2L);
51
+ when(testConnectionRepository.save(any(TestConnection.class))).thenReturn(mockSavedEntity);
52
+
53
+ // When & Then
54
+ mockMvc.perform(post("/api/test/insert-test-data")
55
+ .param("message", customMessage)
56
+ .contentType(MediaType.APPLICATION_JSON))
57
+ .andExpect(status().isOk())
58
+ .andExpect(jsonPath("$.id").value(2L))
59
+ .andExpect(jsonPath("$.message").value(customMessage));
60
+ }
61
+
62
+ @Test
63
+ public void testInsertTestDataWithException() throws Exception {
64
+ // Given
65
+ when(testConnectionRepository.save(any(TestConnection.class)))
66
+ .thenThrow(new RuntimeException("Database error"));
67
+
68
+ // When & Then
69
+ mockMvc.perform(post("/api/test/insert-test-data")
70
+ .contentType(MediaType.APPLICATION_JSON))
71
+ .andExpect(status().isInternalServerError());
72
+ }
73
+ }