Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ out/

### VS Code ###
.vscode/

### Firebase Service Account ###
timetamer-smarcalendar-firebase-adminsdk-fbsvc-8be370036a.json
82 changes: 53 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Spring Boot backend for the "TimeTamer" SmartCalendar application. Provides REST

---

## Quick Start (Development)
## Quick Start
1. Clone repository:
```bash
git clone https://github.com/hse-project-Java-2025/server.git
Expand All @@ -44,6 +44,7 @@ Spring Boot backend for the "TimeTamer" SmartCalendar application. Provides REST
```ini
JWT_SECRET=your_strong_secret_here
CHATGPT_API_KEY=your_openai_api_key
MAIL_PASSWORD=your_smtp_app_password
```
3. Build and run:
```bash
Expand All @@ -56,29 +57,34 @@ Spring Boot backend for the "TimeTamer" SmartCalendar application. Provides REST
---

## Configuration

### Essential Environment Variables
| Variable | Description | Example |
|-------------------|-------------------------------------|-----------------------------|
| `JWT_SECRET` | Secret for JWT token signing | `A$ecretKey!123` |
| `CHATGPT_API_KEY` | OpenAI API key | `sk-...` |
| `DB_URL` | Production DB URL (optional) | `jdbc:postgresql://db:5432` |

### Production Setup
1. Create `application-prod.properties`:
```properties
spring.datasource.url=jdbc:postgresql://your-db-host:5432/smartcalendar
spring.datasource.username=dbuser
spring.datasource.password=dbpassword
spring.jpa.hibernate.ddl-auto=update
```
2. Build executable JAR:
```bash
./gradlew clean build
```
3. Run with production profile:
```bash
java -Dspring.profiles.active=prod -jar build/libs/smartcalendar-*.jar
```
| `JWT_SECRET` | Secret for JWT token signing | `A$ecretKey!123` |
| `CHATGPT_API_KEY` | OpenAI API key | `sk-...` |
| `MAIL_PASSWORD` | SMTP app password for email sending | `your_app_password` |
| `DB_URL` | Production DB URL (optional) | `jdbc:postgresql://db:5432` |


### SMTP Email Notification Setup

To enable email notifications (for collaborative events, invites, etc.), configure the following SMTP settings in your `application.properties`:

| Property | Example Value | Description |
|------------------------------------------------|------------------------------|---------------------------------------------------------------------------------------------|
| `spring.mail.host` | `smtp.gmail.com` | SMTP server host (Gmail example) |
| `spring.mail.port` | `587` | SMTP server port (587 for TLS) |
| `spring.mail.username` | `your_email@gmail.com` | Email account used to send notifications |
| `spring.mail.password` | `${MAIL_PASSWORD}` | App password for the email account (set as environment variable) |
| `spring.mail.properties.mail.smtp.auth` | `true` | Enable SMTP authentication |
| `spring.mail.properties.mail.smtp.starttls.enable` | `true` | Enable STARTTLS encryption |
| `spring.mail.from` | `noreply@ttsc.com` | Sender address shown in emails (must match or be an alias for Gmail accounts) |

**Important notes:**
- For Gmail, you must use an [App Password](https://support.google.com/accounts/answer/185833?hl=en) and have two-factor authentication enabled.
- The value of `spring.mail.from` will only be used if your SMTP provider allows it. Gmail requires this to match your authenticated account or a verified alias.
- For other SMTP providers, adjust the host, port, and credentials accordingly.

---

Expand Down Expand Up @@ -107,13 +113,18 @@ http://localhost:8080/swagger-ui.html
| `/api/users/tasks/{taskId}/status` | PATCH | Update task status |
| `/api/users/tasks/{taskId}` | DELETE | Delete task |

### Event Management
| Endpoint | Method | Description |
|---------------------------------------|--------|------------------------------|
| `/api/users/{userId}/events` | GET | Get user's events |
| `/api/users/{userId}/events` | POST | Create new event |
| `/api/users/events/{eventId}` | PATCH | Update event |
| `/api/users/events/{eventId}` | DELETE | Delete event |
### Event Management (including Collaborative Events)
| Endpoint | Method | Description |
|-----------------------------------------------|--------|--------------------------------------------------|
| `/api/users/{userId}/events` | GET | Get user's events (including shared/collaborative)|
| `/api/users/{userId}/events` | POST | Create new event |
| `/api/users/events/{eventId}` | PATCH | Update event |
| `/api/users/events/{eventId}` | DELETE | Delete event |
| `/api/users/events/{eventId}/invite` | POST | Invite user to event (collaboration) |
| `/api/users/events/{eventId}/accept-invite` | POST | Accept event invitation |
| `/api/users/events/{eventId}/remove-invite` | POST | Remove invitation for user |
| `/api/users/events/{eventId}/remove-participant` | POST | Remove participant from event |
| `/api/users/me/invites` | GET | Get events you are invited to |

### OpenAI Integration
| Endpoint | Method | Description |
Expand All @@ -124,6 +135,17 @@ http://localhost:8080/swagger-ui.html

---

## Collaborative Events

The SmartCalendar supports full collaboration on events:
- **Invite users** to your events by username or email.
- **Accept or decline invitations** to shared events.
- **Remove participants** or invitations at any time.
- **Automatic email notifications** are sent for all key actions (invitation, joining, removal, updates, deletion) with detailed event info.
- **All collaborative features are available via REST API** (see Event Management section above).

---

## Testing
Run tests with:
```bash
Expand All @@ -133,7 +155,9 @@ Run tests with:
- External services (OpenAI) are mocked
- Test coverage reports: `build/reports/tests`

---
### Postman Collection

You can also test all endpoints and collaborative event scenarios using our [Postman collection](https://warped-spaceship-772679.postman.co/workspace/Team-Workspace~558e4b04-2021-4e54-894c-0ad8890eda3d/collection/43149440-fdb46307-d6af-4895-bd4b-5b871c1f6962?action=share&creator=43149440&active-environment=43149440-f5aa59ad-f5b0-484f-923a-2d9403843293)

## CI/CD Pipeline
GitHub Actions workflow (`.github/workflows/ci.yml`):
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'com.google.auth:google-auth-library-oauth2-http:1.19.0'
implementation 'com.google.api-client:google-api-client:2.2.0'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'org.postgresql:postgresql'
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/smartcalendar/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("*")); //TODO: конкретные домены
configuration.setAllowedOrigins(List.of("*"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setExposedHeaders(List.of("Authorization"));
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/smartcalendar/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.Optional;

@RestController
@RequestMapping("/api/auth")
Expand Down Expand Up @@ -79,6 +80,15 @@ public ResponseEntity<?> authenticateUser(@RequestBody User user) {
logger.debug("Authentication successful for user: {}", userDetails.getUsername());
SecurityContextHolder.getContext().setAuthentication(authentication);

if (user.getDeviceToken() != null && !user.getDeviceToken().isBlank()) {
Optional<User> dbUserOpt = userService.findByUsername(userDetails.getUsername());
if (dbUserOpt.isPresent()) {
User dbUser = dbUserOpt.get();
dbUser.setDeviceToken(user.getDeviceToken());
userService.createUser(dbUser);
}
}

String jwt = jwtService.generateToken(userDetails.getUsername());
logger.info("JWT token generated for user: {}", userDetails.getUsername());

Expand Down
Loading