Skip to content

Commit be51fd8

Browse files
authored
Merge pull request #342 from effective-dev-opensource/release/0.0.2
Release/0.0.2
2 parents be206b5 + 6c35bce commit be51fd8

File tree

57 files changed

+491
-287
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+491
-287
lines changed

README.md

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
# Effective Office
2-
## Overview
3-
Effective Office is a comprehensive office management system designed to streamline workplace operations, resource management, and employee interactions. The project consists of a backend server and tablet clients that work together to create an efficient office environment.
42

5-
## Features
6-
- Resource booking and management
7-
- Employee scheduling and coordination
8-
- Office space optimization
9-
- Cross-platform support (android, iOS)
10-
- Secure authentication and authorization
11-
- Real-time updates and notifications
3+
## Goal :dart:
4+
5+
The main goal of the project is the automation of various processes in the office and providing
6+
interesting statistics for employees.
7+
8+
## Technical goal: :wrench:
9+
10+
The main technical task of the project is to create a multi-module application on Kotlin,
11+
trying to focus on the most modern and relevant solutions in this language. Throughout the project,
12+
we tried to use other languages and
13+
technologies as little as possible.
14+
15+
## 📺 Features: (Meeting Room Tablet App)
16+
17+
18+
<img src="media/tablet/demo-tablet.gif" style="height: 50%;" />
19+
20+
### 🔧 Features Overview
21+
22+
| Feature | Description |
23+
|----------------------------|--------------------------------------------------------------|
24+
| Real-time Availability | Displays up-to-date status of meeting rooms |
25+
| Quick Booking | Instantly reserve an available room with a single tap |
26+
| Time-Specific Reservations| Book rooms for specific time slots |
27+
| Booking Cancellation | Cancel existing reservations with ease |
28+
| Early Room Release | Free up the room before the end of the reservation |
29+
| Google Calendar Integration| Syncs all bookings with Google Calendar |
1230

1331
## Architecture
32+
1433
The project follows a client-server architecture:
34+
1535
- **Backend**: Spring Boot application with PostgreSQL database
1636
- **Clients**: Client applications and iOS tablet app
1737
- **Deployment**: Docker-based containerization for easy deployment
@@ -20,15 +40,17 @@ The project follows a client-server architecture:
2040
## Getting Started
2141

2242
### Prerequisites
43+
2344
- Git
2445
- Docker and Docker Compose
2546
- JDK 17 or higher
2647
- Gitleaks (for development)
2748

2849
### Installation
50+
2951
1. Clone the repository:
3052
```
31-
git clone https://github.com/your-organization/effective-office.git
53+
git clone https://github.com/effective-dev-opensource/Effective-Office
3254
cd effective-office
3355
```
3456

@@ -51,13 +73,14 @@ The project follows a client-server architecture:
5173
```
5274

5375
## Project Structure
76+
5477
```
5578
effective-office/
5679
├── backend/ # Server-side application
5780
│ └── README.md # Detailed backend documentation
5881
├── clients/ # Client applications
5982
│ └── README.md # Detailed client documentation
60-
├── iosApp/ # iOS mobile application
83+
├── iosApp/ # iOS tablet application
6184
├── deploy/ # Deployment configurations
6285
│ ├── dev/ # Development environment
6386
│ └── prod/ # Production environment
@@ -68,37 +91,39 @@ effective-office/
6891
```
6992

7093
For detailed documentation:
94+
7195
- [Backend Documentation](./backend/README.md)
7296
- [Client Documentation](./clients/README.md)
7397
- [Build Logic Documentation](./build-logic/README.md)
7498
- [Calendar Integration Documentation](docs/CALENDAR_INTEGRATION.md)
7599

76100
## Development Tools
101+
77102
- **Build System**: Gradle with Kotlin DSL
78103
- **Containerization**: Docker and Docker Compose
79104
- **Security Scanning**: Gitleaks for secret detection
80-
- **CI/CD**: Automated build and deployment pipelines
81105
- **Version Control**: Git with pre-commit hooks
82106

83107
## Code Style & Conventions
108+
84109
- Follow Kotlin coding conventions for backend development
85110
- Use consistent naming patterns across the codebase
86111
- Document public APIs and complex logic
87112
- Run the pre-commit hook to ensure no secrets are committed
88113

89-
## Contributing
90-
1. Ensure you've run the installation script (`./scripts/install.sh`)
91-
2. Follow our [Git Flow](docs/GIT_FLOW.md) for branching and commit conventions
92-
3. Create a feature branch (`git checkout -b feature/amazing-feature`)
93-
4. Commit your changes (`git commit -m 'Add some amazing feature'`)
94-
5. Push to the branch (`git push origin feature/amazing-feature`)
95-
6. Open a Pull Request
114+
## Contributing :raised_hands:
115+
116+
Our project is open-source, so we welcome quality contributions! To make your contribution to the
117+
project efficient and easy to check out, you can familiarize yourself with the project's [git flow
118+
and commit rules](docs/GIT_FLOW.md). If you want to solve an existing issue in the project, you can read the list in
119+
the issues tab in the repository.
96120

97121
## Roadmap
98-
- TODO
122+
- 📺 A TV application is in development, featuring a corporate news and photo feed, event announcements with registration
123+
from an external service, Duolingo and sports leaderboards, and a tracker for the internal currency.
124+
125+
## Authors :writing_hand:
99126

100-
## Authors
101-
- TODO
127+
- [Stanislav Radchenko](https://github.com/Radch-enko)
128+
- [Vitaly Smirnov](https://github.com/KrugarValdes)
102129

103-
## License
104-
- TODO

backend/app/src/main/resources/application.yml

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,20 @@ management:
5555
logging:
5656
level:
5757
root: ${LOG_LEVEL:INFO}
58-
org.hibernate.SQL: INFO
59-
org.hibernate.type.descriptor.sql: INFO
60-
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
6158
band.effective.office.backend: ${LOG_LEVEL:DEBUG}
59+
org.postgresql: WARN
60+
org.hibernate: WARN
61+
org.springframework: WARN
62+
org.springframework.boot.autoconfigure: WARN
63+
org.hibernate.SQL: WARN
64+
org.hibernate.type.descriptor.sql.BasicBinder: WARN
65+
org.hibernate.orm: WARN
66+
com.zaxxer.hikari: INFO
67+
org.apache.coyote.http11: WARN
68+
com.google.api.client.http.HttpTransport: WARN
69+
sun.net.www.protocol.http.HttpURLConnection: WARN
70+
jdk.event.security: WARN
71+
org.springframework.web.filter.CommonsRequestLoggingFilter: DEBUG
6272

6373
application:
6474
url: ${APPLICATION_URL:http://localhost:8080}
@@ -75,7 +85,7 @@ calendar:
7585
default-app-email: ${DEFAULT_APP_EMAIL}
7686
google-credentials: ${GOOGLE_CREDENTIALS_FILE:classpath:google-credentials.json}
7787
application-url: ${APPLICATION_URL}
78-
test-application-url: ${TEST_APPLICATION_URL}
88+
test-application-url: ${TEST_APPLICATION_URL:}
7989
calendars: ${CALENDARS}
80-
test-calendars: ${TEST_CALENDARS}
90+
test-calendars: ${TEST_CALENDARS:}
8191
firebase-credentials: ${FIREBASE_CREDENTIALS:classpath:firebase-credentials.json}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Add count column to workspace_utilities table
2+
ALTER TABLE workspace_utilities
3+
ADD COLUMN count INTEGER NOT NULL DEFAULT 1;
4+
5+
-- Add comment to count column
6+
COMMENT ON COLUMN workspace_utilities.count IS 'Number of this utility in the workspace';

backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/apikey/ApiKeyAuthorizer.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,10 @@ class ApiKeyAuthorizer(
4242
}
4343

4444
val token = authHeader.substring(BEARER_PREFIX.length)
45-
logger.debug("Authorization header found: $token")
4645

4746
try {
4847
// Hash the token and check if it exists in the database
4948
val hashedToken = encryptKey(HASH_ALGORITHM, token)
50-
logger.debug("Hashed token: $hashedToken")
5149
val apiKey = apiKeyRepository.findByKeyValue(hashedToken.lowercase())
5250

5351
if (apiKey == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package band.effective.office.backend.feature.authorization.config
2+
3+
/**
4+
* Centralized configuration for public endpoints that don't require authentication.
5+
* This class provides a single source of truth for paths that should be accessible without authentication.
6+
*/
7+
object PublicEndpoints {
8+
/**
9+
* List of ANT patterns for public endpoints.
10+
*/
11+
val PATTERNS = listOf(
12+
"/auth/**",
13+
"/swagger-ui.html/**",
14+
"/swagger-ui/**",
15+
"/api-docs/**",
16+
"/v3/api-docs/**",
17+
"/api/swagger-ui.html/**",
18+
"/api/swagger-ui/**",
19+
"/api/api-docs/**",
20+
"/api/v3/api-docs/**",
21+
"/api/actuator/**",
22+
"/api/notifications/**",
23+
"/notifications",
24+
)
25+
26+
/**
27+
* Checks if the given URI matches any of the public endpoint patterns.
28+
*
29+
* @param uri The URI to check
30+
* @return True if the URI matches any public endpoint pattern, false otherwise
31+
*/
32+
fun matches(uri: String): Boolean {
33+
return PATTERNS.any { pattern ->
34+
// Convert ANT pattern to regex pattern
35+
val regexPattern = pattern
36+
.replace("/**", "(/.*)?") // /** matches zero or more path segments
37+
.replace("/*", "(/[^/]*)?") // /* matches zero or one path segment
38+
.replace("*", "[^/]*") // * matches zero or more characters within a path segment
39+
40+
uri.matches(Regex("^$regexPattern$"))
41+
}
42+
}
43+
}

backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/config/SecurityConfig.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package band.effective.office.backend.feature.authorization.config
22

3+
import band.effective.office.backend.feature.authorization.config.PublicEndpoints
34
import band.effective.office.backend.feature.authorization.security.JwtAuthenticationFilter
45
import org.springframework.context.annotation.Bean
56
import org.springframework.context.annotation.Configuration
@@ -28,11 +29,7 @@ class SecurityConfig(
2829
.sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
2930
.authorizeHttpRequests { authorize ->
3031
authorize
31-
.requestMatchers("/auth/**").permitAll()
32-
.requestMatchers("/swagger-ui.html/**", "/swagger-ui/**", "/api-docs/**", "/v3/api-docs/**").permitAll()
33-
.requestMatchers("/api/swagger-ui.html/**", "/api/swagger-ui/**", "/api/api-docs/**", "/api/v3/api-docs/**").permitAll()
34-
.requestMatchers("/actuator/**").permitAll()
35-
.requestMatchers("/notifications/**").permitAll()
32+
.requestMatchers(*PublicEndpoints.PATTERNS.toTypedArray()).permitAll()
3633
.anyRequest().authenticated()
3734
}
3835
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)

backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/security/JwtAuthenticationFilter.kt

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package band.effective.office.backend.feature.authorization.security
22

33
import band.effective.office.backend.core.data.ErrorDto
4+
import band.effective.office.backend.feature.authorization.config.PublicEndpoints
45
import band.effective.office.backend.feature.authorization.exception.AuthorizationException
56
import band.effective.office.backend.feature.authorization.exception.AuthorizationErrorCodes
67
import band.effective.office.backend.feature.authorization.service.AuthorizationService
@@ -29,17 +30,13 @@ class JwtAuthenticationFilter(
2930
private val logger = LoggerFactory.getLogger(this::class.java)
3031

3132
/**
32-
* Checks if the request is for Swagger UI or API docs.
33-
*
34-
* @param requestURI The request URI to check
35-
* @return True if the request is for Swagger UI or API docs, false otherwise
33+
* Determines whether the filter should not be applied to this request.
34+
* This method is called by the OncePerRequestFilter before doFilterInternal.
35+
*
36+
* @param request The HTTP request
37+
* @return True if the filter should not be applied, false otherwise
3638
*/
37-
private fun isSwaggerUIRequest(requestURI: String): Boolean { // TODO fix this hacky code
38-
return requestURI.contains("/swagger-ui") ||
39-
requestURI.contains("/api-docs") ||
40-
requestURI.contains("/v3/api-docs") ||
41-
requestURI.contains("/notifications")
42-
}
39+
override fun shouldNotFilter(request: HttpServletRequest): Boolean = PublicEndpoints.matches(request.requestURI)
4340

4441
/**
4542
* Filters incoming requests and attempts to authenticate them.
@@ -53,13 +50,6 @@ class JwtAuthenticationFilter(
5350
response: HttpServletResponse,
5451
filterChain: FilterChain
5552
) {
56-
// Check if the request is for Swagger UI or API docs
57-
val requestURI = request.requestURI
58-
if (isSwaggerUIRequest(requestURI)) {
59-
logger.debug("Skipping authorization for Swagger UI request: {}", requestURI)
60-
filterChain.doFilter(request, response)
61-
return
62-
}
6353

6454
try {
6555
// Attempt to authorize the request

backend/feature/booking/calendar/google/src/main/kotlin/band/effective/office/backend/feature/booking/calendar/google/GoogleCalendarProvider.kt

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,7 @@ class GoogleCalendarProvider(
9090
calendar.events().delete(defaultCalendar, eventId).execute()
9191
} catch (e: GoogleJsonResponseException) {
9292
logger.error("Failed to delete event: {}", e.details)
93-
if (e.statusCode != 404 && e.statusCode != 410) {
94-
throw e
95-
}
96-
// If the event doesn't exist (404) or has been deleted (410), ignore the exception
97-
logger.warn("Event with ID {} not found or already deleted", eventId)
93+
throw e
9894
}
9995
}
10096

@@ -243,10 +239,11 @@ class GoogleCalendarProvider(
243239
}
244240

245241
private fun convertToGoogleEvent(booking: Booking, workspaceCalendarId: String? = null): Event {
242+
val ownerEmail = booking.owner?.email ?: defaultCalendar
246243
val event = Event()
247244
.setSummary("Meet${booking.owner?.let { " ${it.firstName} ${it.lastName}" }.orEmpty()}")
248245
.setDescription(
249-
"${booking.owner?.email} - почта организатора"
246+
"$ownerEmail - почта организатора"
250247
)
251248
.setStart(createEventDateTime(booking.beginBooking.toEpochMilli()))
252249
.setEnd(createEventDateTime(booking.endBooking.toEpochMilli()))
@@ -262,7 +259,7 @@ class GoogleCalendarProvider(
262259
}.toMutableList()
263260

264261
// Add the owner as the organizer
265-
booking.owner?.email?.let { event.organizer = Event.Organizer().setEmail(it) }
262+
event.organizer = Event.Organizer().setEmail(ownerEmail)
266263

267264
// Add workspace as an attendee if workspaceCalendarId is provided
268265
workspaceCalendarId?.let {
@@ -343,6 +340,11 @@ class GoogleCalendarProvider(
343340
matchResult?.groupValues?.get(1)
344341
}
345342

343+
// Determine if the booking is editable based on the organizer
344+
// If the organizer is the defaultCalendar, then the booking is editable
345+
// Otherwise, it's not editable (created from Google Calendar)
346+
val isEditable = organizer == defaultCalendar
347+
346348
return Booking(
347349
id = event.id,
348350
owner = owner,
@@ -351,7 +353,8 @@ class GoogleCalendarProvider(
351353
beginBooking = Instant.ofEpochMilli(event.start.dateTime.value),
352354
endBooking = Instant.ofEpochMilli(event.end.dateTime.value),
353355
recurrence = RecurrenceRuleConverter.fromGoogleRecurrenceRule(event.recurrence),
354-
recurringBookingId = recurringBookingIdStr
356+
recurringBookingId = recurringBookingIdStr,
357+
isEditable = isEditable
355358
)
356359
}
357360

backend/feature/booking/core/src/main/kotlin/band/effective/office/backend/feature/booking/core/domain/model/Booking.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ data class Booking(
2323
val beginBooking: Instant,
2424
val endBooking: Instant,
2525
val recurrence: RecurrenceModel? = null,
26-
val recurringBookingId: String? = null // ID of the recurring booking this booking belongs to
26+
val recurringBookingId: String? = null, // ID of the recurring booking this booking belongs to
27+
val isEditable: Boolean = true, // Flag indicating if booking can be edited/deleted from tablet client
2728
)

backend/feature/booking/core/src/main/kotlin/band/effective/office/backend/feature/booking/core/dto/BookingDto.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ data class BookingDto(
4141
val recurrence: RecurrenceDto? = null,
4242

4343
@Schema(description = "ID of the recurring booking this booking belongs to")
44-
val recurringBookingId: String? = null
44+
val recurringBookingId: String? = null,
45+
46+
@Schema(description = "Flag indicating if booking can be edited/deleted from tablet client", example = "true")
47+
val isEditable: Boolean = true
4548
) {
4649
companion object {
4750
/**
@@ -56,7 +59,8 @@ data class BookingDto(
5659
beginBooking = booking.beginBooking.toEpochMilli(),
5760
endBooking = booking.endBooking.toEpochMilli(),
5861
recurrence = booking.recurrence?.let { RecurrenceDto.fromDomain(it) },
59-
recurringBookingId = booking.recurringBookingId
62+
recurringBookingId = booking.recurringBookingId,
63+
isEditable = booking.isEditable
6064
)
6165
}
6266
}

0 commit comments

Comments
 (0)