├── clients
└── tablet
│ ├── feature
│ ├── settings
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── commonMain
│ │ │ ├── composeResources
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ └── values-ru
│ │ │ │ └── strings_ru.xml
│ │ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ └── feature
│ │ │ └── settings
│ │ │ ├── Intent.kt
│ │ │ ├── State.kt
│ │ │ └── components
│ │ │ └── TitleView.kt
│ ├── main
│ │ ├── src
│ │ │ └── commonMain
│ │ │ │ └── kotlin
│ │ │ │ └── band
│ │ │ │ └── effective
│ │ │ │ └── office
│ │ │ │ └── tablet
│ │ │ │ └── feature
│ │ │ │ └── main
│ │ │ │ ├── presentation
│ │ │ │ ├── main
│ │ │ │ │ ├── Label.kt
│ │ │ │ │ ├── Intent.kt
│ │ │ │ │ ├── navigation
│ │ │ │ │ │ └── ModalWindowsConfig.kt
│ │ │ │ │ └── State.kt
│ │ │ │ └── freeuproom
│ │ │ │ │ ├── Intent.kt
│ │ │ │ │ └── State.kt
│ │ │ │ ├── di
│ │ │ │ └── MainScreenModule.kt
│ │ │ │ ├── domain
│ │ │ │ ├── GetRoomIndexUseCase.kt
│ │ │ │ ├── GetTimeToNextEventUseCase.kt
│ │ │ │ └── CurrentTimeHolder.kt
│ │ │ │ └── components
│ │ │ │ └── uiComponent
│ │ │ │ └── FreeRoomInfoComponent.kt
│ │ └── build.gradle.kts
│ ├── slot
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ └── feature
│ │ │ └── slot
│ │ │ ├── presentation
│ │ │ ├── State.kt
│ │ │ ├── SlotIntent.kt
│ │ │ ├── SlotUi.kt
│ │ │ └── mapper
│ │ │ │ └── SlotUiMapper.kt
│ │ │ ├── di
│ │ │ └── SlotDiModule.kt
│ │ │ └── domain
│ │ │ └── usecase
│ │ │ └── GetSlotsByRoomUseCase.kt
│ ├── fastbooking
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ └── feature
│ │ │ └── fastBooking
│ │ │ └── presentation
│ │ │ ├── Intent.kt
│ │ │ └── State.kt
│ └── bookingEditor
│ │ ├── build.gradle.kts
│ │ └── src
│ │ └── commonMain
│ │ ├── kotlin
│ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ └── feature
│ │ │ └── bookingEditor
│ │ │ ├── di
│ │ │ └── BookingEditorModule.kt
│ │ │ └── presentation
│ │ │ ├── mapper
│ │ │ ├── UpdateEventComponentStateToEventInfoMapper.kt
│ │ │ └── EventInfoMapper.kt
│ │ │ └── Intent.kt
│ │ └── composeResources
│ │ ├── values
│ │ └── strings.xml
│ │ └── values-ru
│ │ └── strings_ru.xml
│ ├── composeApp
│ └── src
│ │ ├── commonMain
│ │ ├── kotlin
│ │ │ └── band
│ │ │ │ └── effective
│ │ │ │ └── office
│ │ │ │ └── tablet
│ │ │ │ ├── isDebug.kt
│ │ │ │ ├── LoggerInitializer.kt
│ │ │ │ ├── di
│ │ │ │ ├── FirebaseTopicsModule.kt
│ │ │ │ ├── AppModule.kt
│ │ │ │ └── KoinInitializer.kt
│ │ │ │ ├── time
│ │ │ │ └── TimeReceiver.kt
│ │ │ │ ├── App.kt
│ │ │ │ └── components
│ │ │ │ └── VersionOverlay.kt
│ │ └── composeResources
│ │ │ ├── font
│ │ │ └── IndieFlower-Regular.ttf
│ │ │ ├── values
│ │ │ └── strings.xml
│ │ │ └── drawable
│ │ │ ├── ic_dark_mode.xml
│ │ │ ├── ic_rotate_right.xml
│ │ │ ├── ic_cyclone.xml
│ │ │ └── ic_light_mode.xml
│ │ ├── androidMain
│ │ ├── kotlin
│ │ │ └── band
│ │ │ │ └── effective
│ │ │ │ └── office
│ │ │ │ └── tablet
│ │ │ │ ├── isDebug.android.kt
│ │ │ │ ├── utils
│ │ │ │ ├── MessageType.kt
│ │ │ │ ├── KioskCommand.kt
│ │ │ │ ├── KioskCommandBus.kt
│ │ │ │ ├── MessageValidator.kt
│ │ │ │ ├── KioskManager.kt
│ │ │ │ └── KioskLifecycleObserver.kt
│ │ │ │ └── AdminReceiver.kt
│ │ └── res
│ │ │ ├── values
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_round.png
│ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ └── xml
│ │ │ └── device_admin_receiver.xml
│ │ └── iosMain
│ │ └── kotlin
│ │ ├── band
│ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ ├── isDebug.ios.kt
│ │ │ ├── Initializers.kt
│ │ │ └── time
│ │ │ └── TimeReceiver.kt
│ │ └── main.kt
│ └── core
│ ├── domain
│ ├── src
│ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ └── core
│ │ │ └── domain
│ │ │ ├── model
│ │ │ ├── RoomsEnum.kt
│ │ │ ├── Booking.kt
│ │ │ ├── Organizer.kt
│ │ │ ├── RoomInfo.kt
│ │ │ ├── EventInfo.kt
│ │ │ ├── Settings.kt
│ │ │ └── Slot.kt
│ │ │ ├── util
│ │ │ ├── LocalDateTimeCropSeconds.kt
│ │ │ ├── SlotUtil.kt
│ │ │ ├── getCorrectDeclension.kt
│ │ │ ├── LocalDateTimeFormatter.kt
│ │ │ ├── BootstrapperTimer.kt
│ │ │ └── LocalDateTimeUtils.kt
│ │ │ ├── useCase
│ │ │ ├── OrganizersInfoUseCase.kt
│ │ │ ├── SetRoomUseCase.kt
│ │ │ ├── CheckSettingsUseCase.kt
│ │ │ ├── TimerUseCase.kt
│ │ │ ├── GetRoomNamesUseCase.kt
│ │ │ ├── GetEventsFlowUseCase.kt
│ │ │ ├── GetRoomByNameUseCase.kt
│ │ │ ├── GetCurrentRoomInfosUseCase.kt
│ │ │ └── GetRoomsInfoUseCase.kt
│ │ │ ├── ErrorWithData.kt
│ │ │ ├── Either.kt
│ │ │ ├── repository
│ │ │ ├── OrganizerRepository.kt
│ │ │ ├── RoomRepository.kt
│ │ │ └── LocalRoomRepository.kt
│ │ │ ├── OfficeTime.kt
│ │ │ ├── ErrorResponse.kt
│ │ │ └── EitherUtils.kt
│ └── build.gradle.kts
│ ├── data
│ ├── build.gradle.kts
│ └── src
│ │ ├── iosMain
│ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ └── core
│ │ │ └── data
│ │ │ └── network
│ │ │ └── HttpClientFactory.ios.kt
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ └── core
│ │ │ └── data
│ │ │ ├── dto
│ │ │ ├── SuccessResponse.kt
│ │ │ ├── workspace
│ │ │ │ ├── WorkspaceZoneDTO.kt
│ │ │ │ ├── UtilityDTO.kt
│ │ │ │ └── WorkspaceDTO.kt
│ │ │ ├── user
│ │ │ │ ├── IntegrationDTO.kt
│ │ │ │ └── UserDTO.kt
│ │ │ └── booking
│ │ │ │ ├── BookingRequestDTO.kt
│ │ │ │ ├── RecurrenceDTO.kt
│ │ │ │ └── BookingResponseDTO.kt
│ │ │ ├── network
│ │ │ └── HttpClientFactory.kt
│ │ │ ├── utils
│ │ │ ├── Buffer.kt
│ │ │ └── Converter.kt
│ │ │ ├── mapper
│ │ │ └── RoomInfoMapper.kt
│ │ │ └── api
│ │ │ ├── Collector.kt
│ │ │ └── UserApi.kt
│ │ └── androidMain
│ │ └── kotlin
│ │ └── band
│ │ └── effective
│ │ └── office
│ │ └── tablet
│ │ └── core
│ │ └── data
│ │ └── network
│ │ └── HttpClientFactory.kt
│ └── ui
│ ├── src
│ ├── commonMain
│ │ ├── kotlin
│ │ │ └── band
│ │ │ │ └── effective
│ │ │ │ └── office
│ │ │ │ └── tablet
│ │ │ │ └── core
│ │ │ │ └── ui
│ │ │ │ ├── utils
│ │ │ │ ├── DateFormatter.kt
│ │ │ │ └── componentCoroutineScope.kt
│ │ │ │ ├── common
│ │ │ │ ├── ModalWindow.kt
│ │ │ │ ├── OrganizerEventView.kt
│ │ │ │ ├── IconSuccess.kt
│ │ │ │ ├── SuccessText.kt
│ │ │ │ ├── AlertButton.kt
│ │ │ │ └── DateTimeView.kt
│ │ │ │ ├── LoadMainScreen.kt
│ │ │ │ ├── date
│ │ │ │ └── DatetimeUiUtils.kt
│ │ │ │ ├── button
│ │ │ │ └── SuccessButton.kt
│ │ │ │ └── booking
│ │ │ │ └── Alert.kt
│ │ └── composeResources
│ │ │ └── drawable
│ │ │ ├── loader_element.xml
│ │ │ ├── allert.xml
│ │ │ ├── check.xml
│ │ │ ├── exit.xml
│ │ │ ├── power_socket.xml
│ │ │ ├── arrow_to_down.xml
│ │ │ ├── arrow_left.xml
│ │ │ ├── arrow_right.xml
│ │ │ ├── arrow_to_right.xml
│ │ │ ├── failure.xml
│ │ │ ├── cross.xml
│ │ │ ├── ethernet.xml
│ │ │ ├── not_wifi.xml
│ │ │ ├── disconnect.xml
│ │ │ └── quantity.xml
│ ├── androidMain
│ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── tablet
│ │ │ └── core
│ │ │ └── ui
│ │ │ └── util
│ │ │ └── DateFormatter.android.kt
│ └── iosMain
│ │ └── kotlin
│ │ └── band
│ │ └── effective
│ │ └── office
│ │ └── tablet
│ │ └── core
│ │ └── ui
│ │ └── utils
│ │ └── DateFormatter.ios.kt
│ └── build.gradle.kts
├── backend
├── app
│ └── src
│ │ └── main
│ │ ├── resources
│ │ ├── .env.properties
│ │ └── env.example
│ │ └── kotlin
│ │ └── band
│ │ └── effective
│ │ └── office
│ │ └── backend
│ │ └── app
│ │ ├── EffectiveOfficeApplication.kt
│ │ └── exception
│ │ └── GlobalExceptionHandler.kt
├── core
│ ├── data
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── backend
│ │ │ └── core
│ │ │ └── data
│ │ │ └── ErrorDto.kt
│ ├── domain
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── backend
│ │ │ └── core
│ │ │ └── domain
│ │ │ ├── model
│ │ │ ├── WorkspaceZone.kt
│ │ │ ├── Utility.kt
│ │ │ ├── Workspace.kt
│ │ │ ├── CalendarId.kt
│ │ │ └── User.kt
│ │ │ └── service
│ │ │ └── UserDomainService.kt
│ └── repository
│ │ ├── build.gradle.kts
│ │ └── src
│ │ └── main
│ │ ├── resources
│ │ ├── db
│ │ │ └── migration
│ │ │ │ ├── V2__add_count_to_workspace_utilities.sql
│ │ │ │ └── V3__add_devices_table.sql
│ │ └── application-repository.yml
│ │ └── kotlin
│ │ └── band
│ │ └── effective
│ │ └── office
│ │ └── backend
│ │ └── core
│ │ └── repository
│ │ └── config
│ │ └── DatabaseConfig.kt
└── feature
│ ├── user
│ ├── build.gradle.kts
│ ├── src
│ │ └── main
│ │ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── backend
│ │ │ └── feature
│ │ │ └── user
│ │ │ ├── config
│ │ │ └── UserRepositoryConfig.kt
│ │ │ └── repository
│ │ │ ├── UserRepository.kt
│ │ │ └── entity
│ │ │ └── UserEntity.kt
│ └── README.md
│ ├── booking
│ ├── core
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ └── kotlin
│ │ │ └── band
│ │ │ └── effective
│ │ │ └── office
│ │ │ └── backend
│ │ │ └── feature
│ │ │ └── booking
│ │ │ └── core
│ │ │ ├── domain
│ │ │ └── model
│ │ │ │ ├── RecurrenceModel.kt
│ │ │ │ └── Booking.kt
│ │ │ ├── exception
│ │ │ └── BookingErrorCodes.kt
│ │ │ └── config
│ │ │ └── CalendarProviderConfig.kt
│ └── calendar
│ │ ├── dummy
│ │ └── build.gradle.kts
│ │ └── google
│ │ └── build.gradle.kts
│ ├── workspace
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── band
│ │ └── effective
│ │ └── office
│ │ └── backend
│ │ └── feature
│ │ └── workspace
│ │ └── core
│ │ ├── repository
│ │ ├── entity
│ │ │ ├── WorkspaceZoneEntity.kt
│ │ │ ├── WorkspaceUtilityEntity.kt
│ │ │ ├── UtilityEntity.kt
│ │ │ ├── WorkspaceEntity.kt
│ │ │ └── CalendarIdEntity.kt
│ │ ├── mapper
│ │ │ ├── CalendarIdMapper.kt
│ │ │ └── WorkspaceMapper.kt
│ │ ├── CalendarIdRepository.kt
│ │ └── WorkspaceRepository.kt
│ │ ├── dto
│ │ ├── WorkspaceZoneDTO.kt
│ │ ├── UtilityDTO.kt
│ │ └── WorkspaceDTO.kt
│ │ └── config
│ │ └── WorkspaceConfig.kt
│ ├── authorization
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── band
│ │ └── effective
│ │ └── office
│ │ └── backend
│ │ └── feature
│ │ └── authorization
│ │ ├── exception
│ │ └── AuthorizationErrorCodes.kt
│ │ ├── apikey
│ │ ├── repository
│ │ │ └── ApiKeyRepository.kt
│ │ ├── entity
│ │ │ └── ApiKey.kt
│ │ └── config
│ │ │ └── ApiKeyConfig.kt
│ │ └── core
│ │ ├── exception
│ │ └── AuthorizationExceptions.kt
│ │ └── Authorizer.kt
│ ├── calendar-subscription
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── band
│ │ └── effective
│ │ └── office
│ │ └── backend
│ │ └── feature
│ │ └── calendar
│ │ └── subscription
│ │ ├── config
│ │ └── ChannelRepositoryConfig.kt
│ │ ├── CalendarSubscriptionModule.kt
│ │ └── repository
│ │ ├── ChannelRepository.kt
│ │ ├── entity
│ │ └── ChannelEntity.kt
│ │ └── mapper
│ │ └── ChannelMapper.kt
│ └── notifications
│ ├── src
│ └── main
│ │ └── kotlin
│ │ └── band
│ │ └── effective
│ │ └── office
│ │ └── backend
│ │ └── feature
│ │ └── notifications
│ │ ├── service
│ │ ├── INotificationSender.kt
│ │ ├── DeviceService.kt
│ │ └── FcmNotificationSender.kt
│ │ ├── dto
│ │ ├── KioskMessageDto.kt
│ │ ├── KioskToggleRequest.kt
│ │ └── DeviceDto.kt
│ │ ├── repository
│ │ ├── DeviceRepository.kt
│ │ └── entity
│ │ │ └── DeviceEntity.kt
│ │ ├── config
│ │ └── NotificationsRepositoryConfig.kt
│ │ └── controller
│ │ └── NotificationDeduplicator.kt
│ └── build.gradle.kts
├── iosApp
├── iosApp
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── ItunesArtwork@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── AccentColor.colorset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Info.plist
│ ├── RootView.swift
│ ├── RootHolder.swift
│ └── iosApp.swift
└── iosApp.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── radchenko.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
│ └── xcuserdata
│ └── radchenko.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── media
└── tablet
│ └── demo-tablet.gif
├── scripts
├── install.sh
└── git-hooks
│ └── pre-commit.sh
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .devcontainer
└── devcontainer.json
├── deploy
├── prod
│ ├── Dockerfile
│ └── .env.example
└── dev
│ ├── Dockerfile
│ └── .env.example
├── gradle.properties
├── .github
└── workflows
│ └── junie.yml
├── .aiignore
├── LICENSE
├── .gitignore
└── settings.gradle.kts
/clients/tablet/feature/settings/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.client.kmp.feature")
3 | }
--------------------------------------------------------------------------------
/backend/app/src/main/resources/.env.properties:
--------------------------------------------------------------------------------
1 | # Path to the .env file relative to the project root
2 | dotenv.path=.env
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/media/tablet/demo-tablet.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/media/tablet/demo-tablet.gif
--------------------------------------------------------------------------------
/scripts/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cd ..
3 | cp scripts/git-hooks/pre-commit.sh .git/hooks/pre-commit
4 | chmod +x .git/hooks/pre-commit
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/kotlin/band/effective/office/tablet/isDebug.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet
2 |
3 | expect val isDebug: Boolean
--------------------------------------------------------------------------------
/backend/core/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.kotlin-common")
3 | }
4 |
5 | dependencies {
6 | implementation(libs.jakarta)
7 | }
--------------------------------------------------------------------------------
/backend/core/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-boot-common")
3 | }
4 |
5 | dependencies {
6 | implementation(libs.jakarta)
7 | }
8 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/isDebug.android.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet
2 |
3 | actual val isDebug: Boolean = BuildConfig.DEBUG
--------------------------------------------------------------------------------
/backend/core/repository/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | implementation(project(":backend:core:domain"))
7 | }
8 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #171717
4 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/composeResources/font/IndieFlower-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/effective-dev-opensource/Effective-Office/HEAD/clients/tablet/composeApp/src/commonMain/composeResources/font/IndieFlower-Regular.ttf
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/iosMain/kotlin/band/effective/office/tablet/isDebug.ios.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet
2 |
3 | import kotlin.experimental.ExperimentalNativeApi
4 |
5 | @OptIn(ExperimentalNativeApi::class)
6 | actual val isDebug: Boolean = Platform.isDebugBinary
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/presentation/main/Label.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.presentation.main
2 |
3 | sealed interface Label {
4 | data class ShowToast(val text: String) : Label
5 | }
6 |
--------------------------------------------------------------------------------
/clients/tablet/feature/slot/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.client.kmp.feature")
3 | }
4 |
5 | compose.resources {
6 | publicResClass = false
7 | packageOfResClass = "band.effective.office.tablet.feature.slot"
8 | generateResClass = auto
9 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/fastbooking/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.client.kmp.feature")
3 | }
4 |
5 | compose.resources {
6 | publicResClass = false
7 | packageOfResClass = "band.effective.office.tablet.feature.fastBooking"
8 | generateResClass = auto
9 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Jun 25 13:54:29 OMST 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Stanislav Radchenko on 04.07.2025.
3 | //
4 |
5 | import Foundation
6 | import ComposeApp
7 | import SwiftUI
8 |
9 | class AppDelegate: NSObject, UIApplicationDelegate {
10 | let rootHolder: RootHolder = RootHolder()
11 | }
12 |
--------------------------------------------------------------------------------
/clients/tablet/feature/bookingEditor/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.client.kmp.feature")
3 | }
4 |
5 | compose.resources {
6 | publicResClass = false
7 | packageOfResClass = "band.effective.office.tablet.feature.bookingEditor"
8 | generateResClass = auto
9 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/settings/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exit
4 | Meeting Rooms
5 | Choose ${nameRoom}
6 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/radchenko.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/backend/feature/user/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | implementation(libs.jakarta)
7 | implementation(libs.jakarta.servlet.api)
8 |
9 | implementation(libs.springdoc.openapi.starter.webmvc.ui)
10 | }
11 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/RoomsEnum.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.model
2 |
3 | enum class RoomsEnum(val nameRoom: String) {
4 | PLUTO("Pluto"),
5 | MOON("Moon"),
6 | ANTARES("Mercury"),
7 | SUN("Sun")
8 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/settings/src/commonMain/composeResources/values-ru/strings_ru.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Выход
4 | Переговорки
5 | Выбрать ${nameRoom}
6 |
--------------------------------------------------------------------------------
/backend/feature/booking/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | implementation(libs.jakarta)
7 | implementation(libs.jakarta.servlet.api)
8 |
9 | implementation(libs.springdoc.openapi.starter.webmvc.ui)
10 | }
11 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Cyclone
3 | Open github
4 | Run
5 | Stop
6 | Theme
7 |
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/presentation/freeuproom/Intent.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.presentation.freeuproom
2 |
3 | sealed interface Intent {
4 | object OnFreeSelectRequest : Intent
5 | object OnCloseWindowRequest : Intent
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/clients/tablet/feature/slot/src/commonMain/kotlin/band/effective/office/tablet/feature/slot/presentation/State.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.slot.presentation
2 |
3 | data class State(
4 | val slots: List
5 | ) {
6 | companion object {
7 | val initValue = State(slots = listOf())
8 | }
9 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/util/LocalDateTimeCropSeconds.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.util
2 |
3 | import kotlinx.datetime.LocalDateTime
4 |
5 | fun LocalDateTime.cropSeconds(): LocalDateTime =
6 | LocalDateTime(year, month, dayOfMonth, hour, minute)
--------------------------------------------------------------------------------
/clients/tablet/feature/settings/src/commonMain/kotlin/band/effective/office/tablet/feature/settings/Intent.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.settings
2 |
3 | sealed interface Intent {
4 | object OnExitApp : Intent
5 | data class ChangeCurrentNameRoom(val nameRoom: String) : Intent
6 | object SaveData : Intent
7 | }
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Java",
3 | "image": "mcr.microsoft.com/devcontainers/java:1-21",
4 | "features": {
5 | "ghcr.io/devcontainers/features/java:1": {
6 | "version": "none",
7 | "installMaven": "true",
8 | "mavenVersion": "3.8.6",
9 | "installGradle": "true"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/fastbooking/src/commonMain/kotlin/band/effective/office/tablet/feature/fastBooking/presentation/Intent.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.fastBooking.presentation
2 |
3 | sealed interface Intent {
4 | data class OnFreeSelectRequest(val room: String) : Intent
5 | object OnCloseWindowRequest : Intent
6 | }
--------------------------------------------------------------------------------
/clients/tablet/core/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.client.kmp.data")
3 | }
4 |
5 | kotlin {
6 | sourceSets {
7 | commonMain.dependencies {
8 | implementation(project(":clients:tablet:core:domain"))
9 | implementation(libs.bundles.koin)
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/backend/core/repository/src/main/resources/db/migration/V2__add_count_to_workspace_utilities.sql:
--------------------------------------------------------------------------------
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';
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/deploy/prod/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM eclipse-temurin:17-jre
2 |
3 | # Устанавливаем curl (если нужен для healthcheck'ов)
4 | RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
5 |
6 | WORKDIR /app
7 |
8 | # Копируем JAR-файл из текущего контекста
9 | COPY *.jar app.jar
10 |
11 | EXPOSE 8080
12 |
13 | ENTRYPOINT ["java", "-jar", "app.jar"]
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Gradle properties
2 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
3 | org.gradle.parallel=true
4 | org.gradle.caching=true
5 |
6 | # Kotlin properties
7 | kotlin.code.style=official
8 | kotlin.incremental=true
9 |
10 | # Project properties
11 | group=band.effective.office
12 | version=1.0.1
13 |
14 | android.useAndroidX=true
15 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/iosMain/kotlin/band/effective/office/tablet/core/data/network/HttpClientFactory.ios.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.network
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.engine.darwin.Darwin
5 |
6 | actual object HttpClientFactory {
7 | actual fun createHttpClient(): HttpClient = HttpClient(Darwin)
8 | }
--------------------------------------------------------------------------------
/deploy/dev/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM eclipse-temurin:17-jre
2 |
3 | # Устанавливаем curl (если нужен для healthcheck'ов)
4 | RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
5 |
6 | WORKDIR /app
7 |
8 | # Копируем JAR-файл из текущего контекста
9 | COPY *.jar app.jar
10 |
11 | EXPOSE 8080
12 |
13 |
14 | ENTRYPOINT ["java", "-jar", "app.jar"]
--------------------------------------------------------------------------------
/scripts/git-hooks/pre-commit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "🔍 Running Gitleaks scan..."
4 |
5 | gitleaks detect --source . --log-opts "--all" --verbose
6 |
7 | if [ $? -ne 0 ]; then
8 | echo "🚨 Gitleaks detected potential secrets in staged changes. Commit aborted."
9 | exit 1
10 | fi
11 |
12 | echo "✅ Gitleaks passed. Proceeding with commit."
13 | exit 0
--------------------------------------------------------------------------------
/backend/feature/workspace/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | implementation(libs.jakarta)
7 | implementation(libs.jakarta.servlet.api)
8 |
9 | implementation(libs.springdoc.openapi.starter.webmvc.ui)
10 |
11 | implementation(project(":backend:feature:booking:core"))
12 | }
13 |
--------------------------------------------------------------------------------
/backend/feature/booking/calendar/dummy/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | implementation(project(":backend:feature:booking:core"))
7 |
8 | implementation(libs.jakarta)
9 | implementation(libs.jakarta.servlet.api)
10 |
11 | implementation(libs.springdoc.openapi.starter.webmvc.ui)
12 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/kotlin/band/effective/office/tablet/LoggerInitializer.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet
2 |
3 | import io.github.aakira.napier.DebugAntilog
4 | import io.github.aakira.napier.Napier
5 |
6 | class LoggerInitializer {
7 | fun init() {
8 | if (isDebug) {
9 | Napier.base(DebugAntilog())
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/utils/DateFormatter.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.utils
2 |
3 | import kotlinx.datetime.LocalDateTime
4 |
5 | @OptIn(kotlinx.datetime.format.FormatStringsInDatetimeFormats::class)
6 | expect fun LocalDateTime.toLocalisedString(pattern: String): String
7 | expect fun getCurrentLanguageCode(): String
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/iosMain/kotlin/main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.window.ComposeUIViewController
2 | import band.effective.office.tablet.App
3 | import band.effective.office.tablet.root.RootComponent
4 | import platform.UIKit.UIViewController
5 |
6 | fun rootViewController(root: RootComponent): UIViewController =
7 | ComposeUIViewController {
8 | App(root)
9 | }
10 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/kotlin/band/effective/office/tablet/di/FirebaseTopicsModule.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.di
2 |
3 | import org.koin.core.qualifier.named
4 | import org.koin.dsl.module
5 |
6 | val firebaseTopicsModule = module {
7 | single(named("FireBaseTopics")) { listOf("effectiveoffice-workspace", "effectiveoffice-user", "effectiveoffice-booking","kiosk-commands") }
8 | }
--------------------------------------------------------------------------------
/backend/feature/authorization/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | // Spring Security
7 | implementation(libs.spring.boot.starter.security)
8 |
9 | // Spring Data JPA
10 | implementation(libs.spring.boot.starter.data.jpa)
11 |
12 | implementation(libs.jakarta)
13 | implementation(libs.jakarta.servlet.api)
14 | }
15 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/SuccessResponse.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents a successful response from the server.
7 | * @property success Indicates whether the operation was successful
8 | */
9 | @Serializable
10 | data class SuccessResponse(val success: Boolean = true)
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/Booking.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.model
2 |
3 | data class Booking(
4 | val nameRoom: String,
5 | val roomId: String,
6 | val eventInfo: EventInfo
7 | ) {
8 | companion object {
9 | val default = Booking(nameRoom = "", roomId = "", eventInfo = EventInfo.Companion.emptyEvent)
10 | }
11 | }
--------------------------------------------------------------------------------
/backend/feature/calendar-subscription/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | // Google Calendar API
7 | implementation("com.google.apis:google-api-services-calendar:v3-rev411-1.25.0")
8 | implementation("com.google.auth:google-auth-library-oauth2-http:1.3.0")
9 | implementation("com.google.oauth-client:google-oauth-client-jetty:1.34.1")
10 | }
11 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/utils/MessageType.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.utils
2 |
3 | enum class MessageType(val value: String) {
4 | KIOSK_TOGGLE("KIOSK_TOGGLE"),
5 | UNKNOWN("UNKNOWN");
6 |
7 | companion object {
8 | fun fromString(value: String?): MessageType {
9 | return entries.find { it.value == value } ?: UNKNOWN
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/presentation/freeuproom/State.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.presentation.freeuproom
2 |
3 | data class State(
4 | val isLoad: Boolean,
5 | val isSuccess: Boolean
6 | ) {
7 | companion object {
8 | val defaultState = State(
9 | isLoad = false,
10 | isSuccess = true
11 | )
12 | }
13 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/OrganizersInfoUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import band.effective.office.tablet.core.domain.repository.OrganizerRepository
4 |
5 | /**Use case for get organizers list*/
6 | class OrganizersInfoUseCase(private val repository: OrganizerRepository) {
7 | suspend operator fun invoke() = repository.getOrganizersList()
8 | }
--------------------------------------------------------------------------------
/deploy/dev/.env.example:
--------------------------------------------------------------------------------
1 | POSTGRES_DB=yourdatabasename
2 | POSTGRES_USER=youruser
3 | POSTGRES_PASSWORD=youruser
4 | SPRING_DATASOURCE_URL=yourpathtodatabase
5 |
6 | # Application Configuration
7 | MIGRATIONS_ENABLE=true
8 | LOG_LEVEL=DEBUG
9 |
10 | CALENDAR_APPLICATION_NAME=yourcalendarapplicationname
11 | DEFAULT_CALENDAR=yourdefaultcalendar
12 | TEST_CALENDARS=your calendars
13 | TEST_APPLICATION_URL=your test application url
14 |
15 | LABEL=yourdevdomain
--------------------------------------------------------------------------------
/deploy/prod/.env.example:
--------------------------------------------------------------------------------
1 | POSTGRES_DB=yourdatabasename
2 | POSTGRES_USER=youruser
3 | POSTGRES_PASSWORD=youruser
4 | SPRING_DATASOURCE_URL=yourpathtodatabase
5 |
6 | # Application Configuration
7 | MIGRATIONS_ENABLE=true
8 | LOG_LEVEL=DEBUG
9 |
10 | CALENDAR_APPLICATION_NAME=yourcalendarapplicationname
11 | DEFAULT_CALENDAR=yourdefaultcalendar
12 | TEST_CALENDARS=your calendars
13 | TEST_APPLICATION_URL=your test application url
14 |
15 | LABEL=yourdevdomain
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.time
2 |
3 | import kotlinx.coroutines.flow.StateFlow
4 | import kotlinx.datetime.LocalDateTime
5 |
6 | /**
7 | * A receiver that emits the current time.
8 | */
9 | expect class TimeReceiver {
10 | /**
11 | * A flow that emits the current time.
12 | */
13 | val currentTime: StateFlow
14 | }
15 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/SetRoomUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import band.effective.office.tablet.core.domain.model.SettingsManager
4 |
5 | /**Use case for set settings*/
6 | class SetRoomUseCase {
7 | /**save current room name*/
8 | operator fun invoke(nameRoom: String) =
9 | SettingsManager.current().updateSettings(nameRoom)
10 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/util/SlotUtil.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.util
2 |
3 | import band.effective.office.tablet.core.domain.model.Slot
4 |
5 | fun Slot.freeTime(): Int {
6 | val startInstant = start.asInstant
7 | val finishInstant = finish.asInstant
8 | val duration = finishInstant - startInstant
9 | return duration.inWholeMinutes.toInt().coerceAtLeast(0)
10 | }
--------------------------------------------------------------------------------
/backend/core/data/src/main/kotlin/band/effective/office/backend/core/data/ErrorDto.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.core.data
2 |
3 | /**
4 | * DTO for error response.
5 | * This class represents the standardized error response format for all services.
6 | *
7 | * @property message Detailed description of the error
8 | * @property code Error code (not HTTP status code)
9 | */
10 | data class ErrorDto(
11 | val message: String,
12 | val code: Int
13 | )
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/CheckSettingsUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import band.effective.office.tablet.core.domain.model.SettingsManager
4 |
5 | /**use case for get settings values*/
6 | class CheckSettingsUseCase {
7 | /**Get current room from settings*/
8 | operator fun invoke() =
9 | SettingsManager.current().checkCurrentRoom()
10 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/main/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.client.kmp.feature")
3 | }
4 |
5 | kotlin {
6 | sourceSets {
7 | commonMain.dependencies {
8 | implementation(project(":clients:tablet:feature:slot"))
9 | }
10 | }
11 | }
12 |
13 | compose.resources {
14 | publicResClass = false
15 | packageOfResClass = "band.effective.office.tablet.feature.main"
16 | generateResClass = auto
17 | }
18 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/workspace/WorkspaceZoneDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto.workspace
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents a zone in the workspace.
7 | * @property id Unique identifier
8 | * @property name Name of the zone
9 | */
10 | @Serializable
11 | data class WorkspaceZoneDTO(
12 | val id: String,
13 | val name: String
14 | )
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/ErrorWithData.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain
2 |
3 | data class ErrorWithData(
4 | val error: ErrorResponse,
5 | val saveData: T?
6 | ) {
7 | fun map(mapper: (T) -> NewType): ErrorWithData {
8 | return if (saveData != null) ErrorWithData(error, mapper(saveData))
9 | else ErrorWithData(error, null)
10 | }
11 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/Organizer.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class Organizer(val fullName: String, val id: String, val email: String?) {
7 | companion object {
8 | val default = Organizer(
9 | fullName = "", id = "", email = null
10 | )
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/xcuserdata/radchenko.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | iosApp.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/utils/KioskCommand.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.utils
2 |
3 | sealed interface KioskCommand {
4 | object Enable : KioskCommand
5 | object Disable : KioskCommand
6 | }
7 |
8 | object KioskCommandMapper {
9 | fun mapToKioskCommand(isKioskModeActive: Boolean): KioskCommand {
10 | return if (isKioskModeActive) KioskCommand.Enable else KioskCommand.Disable
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/common/ModalWindow.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.common
2 |
3 | /**
4 | * Interface for modal windows
5 | *
6 | * This is a marker interface that can be implemented by components that represent modal windows.
7 | */
8 | interface ModalWindow {
9 | // This is an empty interface, likely used as a marker interface
10 | // or as a placeholder for future implementation.
11 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/presentation/main/Intent.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.presentation.main
2 |
3 | sealed interface Intent {
4 | object OnOpenFreeRoomModal : Intent
5 | object RebootRequest : Intent
6 | data class OnSelectRoom(val index: Int) : Intent
7 | data class OnFastBooking(val minDuration: Int) : Intent
8 | data class OnUpdateSelectDate(val updateInDays: Int) : Intent
9 | }
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/network/HttpClientFactory.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.network
2 |
3 | import io.ktor.client.HttpClient
4 |
5 | /**
6 | * Factory for creating platform-specific HTTP clients
7 | */
8 | expect object HttpClientFactory {
9 | /**
10 | * Creates a platform-specific HTTP client
11 | * @return HttpClient instance
12 | */
13 | fun createHttpClient(): HttpClient
14 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/util/getCorrectDeclension.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.util
2 |
3 | fun getCorrectDeclension(
4 | number: Int, nominativeCase: String, genitive: String, genitivePlural: String
5 | ): String = if (number in 10..20) genitivePlural
6 | else when (number % 10) {
7 | 0 -> genitivePlural
8 | 1 -> nominativeCase
9 | 2, 3, 4 -> genitive
10 | else -> genitivePlural
11 | }
--------------------------------------------------------------------------------
/backend/core/domain/src/main/kotlin/band/effective/office/backend/core/domain/model/WorkspaceZone.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.core.domain.model
2 |
3 | import java.util.*
4 |
5 | /**
6 | * Domain model representing a zone in the office where workspaces are located.
7 | *
8 | * @property id The unique identifier for the workspace zone
9 | * @property name The name of the workspace zone
10 | */
11 | data class WorkspaceZone(
12 | val id: UUID,
13 | val name: String
14 | )
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/loader_element.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/feature/slot/src/commonMain/kotlin/band/effective/office/tablet/feature/slot/di/SlotDiModule.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.slot.di
2 |
3 | import band.effective.office.tablet.feature.slot.domain.usecase.GetSlotsByRoomUseCase
4 | import band.effective.office.tablet.feature.slot.presentation.mapper.SlotUiMapper
5 | import org.koin.dsl.module
6 |
7 | val slotDiModule = module {
8 | single { SlotUiMapper() }
9 | single { GetSlotsByRoomUseCase(get()) }
10 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/di/MainScreenModule.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.di
2 |
3 | import band.effective.office.tablet.feature.main.domain.GetRoomIndexUseCase
4 | import band.effective.office.tablet.feature.main.domain.GetTimeToNextEventUseCase
5 | import org.koin.dsl.module
6 |
7 | val mainScreenModule = module {
8 | single { GetRoomIndexUseCase(get()) }
9 | single { GetTimeToNextEventUseCase() }
10 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/AdminReceiver.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet
2 |
3 | import android.app.admin.DeviceAdminReceiver
4 | import android.content.ComponentName
5 | import android.content.Context
6 |
7 | class AdminReceiver : DeviceAdminReceiver() {
8 | companion object {
9 | fun getComponentName(context: Context): ComponentName {
10 | return ComponentName(context.applicationContext, AdminReceiver::class.java)
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/iosMain/kotlin/band/effective/office/tablet/Initializers.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet
2 |
3 | import band.effective.office.tablet.core.domain.model.SettingsManager
4 | import band.effective.office.tablet.di.KoinInitializer
5 | import com.russhwolf.settings.KeychainSettings
6 |
7 | class Initializers {
8 |
9 | fun init() {
10 | LoggerInitializer().init()
11 | KoinInitializer().init()
12 | SettingsManager.init(KeychainSettings("effectiveOffice"))
13 | }
14 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | en
9 | CFBundleLocalizations
10 |
11 | en
12 | ru
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/allert.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/feature/slot/src/commonMain/kotlin/band/effective/office/tablet/feature/slot/presentation/SlotIntent.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.slot.presentation
2 |
3 | import kotlinx.datetime.LocalDateTime
4 |
5 | sealed interface SlotIntent {
6 | data class ClickToEdit(val slot: SlotUi) : SlotIntent
7 | data class ClickToToggle(val slot: SlotUi.MultiSlot) : SlotIntent
8 | data class UpdateRequest(val room: String, val newDate: LocalDateTime) : SlotIntent
9 | data object InactivityTimeout: SlotIntent
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/user/IntegrationDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto.user
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents an integration with an external service.
7 | * @property id Unique identifier
8 | * @property name Name of the integration
9 | * @property value Value of the integration
10 | */
11 | @Serializable
12 | data class IntegrationDTO(
13 | val id: String,
14 | val name: String,
15 | val value: String
16 | )
--------------------------------------------------------------------------------
/iosApp/iosApp/RootView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootView.swift
3 | // iosApp
4 | //
5 | // Created by Stanislav Radchenko on 04.07.2025.
6 | //
7 | import ComposeApp
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct RootView: UIViewControllerRepresentable {
12 | let root: RootComponent
13 |
14 | func makeUIViewController(context: Context) -> UIViewController {
15 | return MainKt.rootViewController(root: root)
16 | }
17 |
18 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/composeResources/drawable/ic_dark_mode.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/check.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/exit.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/kotlin/band/effective/office/tablet/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.di
2 |
3 | import band.effective.office.tablet.BuildKonfig
4 | import band.effective.office.tablet.isDebug
5 | import org.koin.core.qualifier.named
6 | import org.koin.dsl.module
7 |
8 | val appModule = module {
9 | single(qualifier = named("ApiUrl")) {
10 | if (isDebug) BuildKonfig.API_URL_DEBUG else BuildKonfig.API_URL_RELEASE
11 | }
12 | single(qualifier = named("ApiKey")) {
13 | BuildKonfig.API_KEY
14 | }
15 | }
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/androidMain/kotlin/band/effective/office/tablet/core/data/network/HttpClientFactory.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.network
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.engine.okhttp.OkHttp
5 |
6 | /**
7 | * Android-specific implementation of HttpClientFactory
8 | */
9 | actual object HttpClientFactory {
10 | /**
11 | * Creates an Android-specific HTTP client using OkHttp engine
12 | * @return HttpClient instance
13 | */
14 | actual fun createHttpClient(): HttpClient = HttpClient(OkHttp)
15 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.client.kmp.domain")
3 | }
4 |
5 | kotlin {
6 | sourceSets {
7 | commonMain.dependencies {
8 | api(libs.kotlinx.datetime)
9 | implementation(libs.kotlin.coroutines.core)
10 | implementation(libs.settings)
11 | implementation(libs.bundles.koin)
12 | }
13 | androidMain.dependencies {
14 | implementation(libs.coroutines.android)
15 | implementation(libs.koin.android)
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.github/workflows/junie.yml:
--------------------------------------------------------------------------------
1 | name: Junie
2 | run-name: Junie run ${{ inputs.run_id }}
3 |
4 | permissions:
5 | contents: write
6 |
7 | on:
8 | workflow_dispatch:
9 | inputs:
10 | run_id:
11 | description: "id of workflow process"
12 | required: true
13 | workflow_params:
14 | description: "stringified params"
15 | required: true
16 |
17 | jobs:
18 | call-workflow-passing-data:
19 | uses: jetbrains-junie/junie-workflows/.github/workflows/ej-issue.yml@main
20 | with:
21 | workflow_params: ${{ inputs.workflow_params }}
22 |
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/service/INotificationSender.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.service
2 |
3 | /**
4 | * Interface for sending notifications on topics
5 | */
6 | interface INotificationSender {
7 |
8 | /**
9 | * Sends message about topic modification
10 | */
11 | fun sendEmptyMessage(topic: String)
12 |
13 | /**
14 | * Sends message with data about topic modification
15 | */
16 | fun sendDataMessage(topic: String, data: Map)
17 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/power_socket.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/feature/bookingEditor/src/commonMain/kotlin/band/effective/office/tablet/feature/bookingEditor/di/BookingEditorModule.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.bookingEditor.di
2 |
3 | import band.effective.office.tablet.feature.bookingEditor.presentation.mapper.EventInfoMapper
4 | import band.effective.office.tablet.feature.bookingEditor.presentation.mapper.UpdateEventComponentStateToEventInfoMapper
5 | import org.koin.dsl.module
6 |
7 | val bookingEditorModule = module {
8 | single { EventInfoMapper() }
9 | single { UpdateEventComponentStateToEventInfoMapper() }
10 | }
--------------------------------------------------------------------------------
/backend/app/src/main/resources/env.example:
--------------------------------------------------------------------------------
1 | SPRING_DATASOURCE_URL=jdbc:postgresql://0.0.0.0:5432/effectiveoffice
2 | SPRING_DATASOURCE_USERNAME=postgres
3 | SPRING_DATASOURCE_PASSWORD=postgres
4 |
5 | MIGRATIONS_ENABLE=true
6 | APPLICATION_URL=http://localhost:8080
7 | LOG_LEVEL=debug
8 |
9 | CALENDAR_APPLICATION_NAME=YourApplicationName
10 | CALENDAR_DELEGATED_USER=yourdelegateduseremail
11 | GOOGLE_CREDENTIALS_FILE=classpath:google-credentials.json
12 |
13 | DEFAULT_APP_EMAIL=yourdefaultappemail
14 | FIREBASE_CREDENTIALS=classpath:firebase-credentials.json
15 | DEFAULT_CALENDAR=yourdefaultcalendar
16 |
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/GetRoomIndexUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.domain
2 |
3 | import band.effective.office.tablet.core.domain.model.RoomInfo
4 | import band.effective.office.tablet.core.domain.useCase.CheckSettingsUseCase
5 |
6 | class GetRoomIndexUseCase(
7 | private val checkSettingsUseCase: CheckSettingsUseCase,
8 | ) {
9 | operator fun invoke(rooms: List) = rooms.indexOfFirst { it.name == checkSettingsUseCase() }.run {
10 | if (this < 0) 0 else this
11 | }
12 | }
--------------------------------------------------------------------------------
/backend/core/domain/src/main/kotlin/band/effective/office/backend/core/domain/model/Utility.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.core.domain.model
2 |
3 | import java.util.*
4 |
5 | /**
6 | * Domain model representing a utility available in a workspace.
7 | *
8 | * @property id The unique identifier for the utility
9 | * @property name The name of the utility
10 | * @property iconUrl The URL for the utility's icon
11 | * @property count The quantity of this utility
12 | */
13 | data class Utility(
14 | val id: UUID,
15 | val name: String,
16 | val iconUrl: String,
17 | val count: Int
18 | )
--------------------------------------------------------------------------------
/backend/feature/booking/core/src/main/kotlin/band/effective/office/backend/feature/booking/core/domain/model/RecurrenceModel.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.booking.core.domain.model
2 |
3 | /**
4 | * Represents a recurrence pattern for a booking.
5 | */
6 | data class RecurrenceModel(
7 | val interval: Int? = null,
8 | val freq: String,
9 | val count: Int? = null,
10 | val until: Long? = null,
11 | val byDay: List = emptyList(),
12 | val byMonth: List = emptyList(),
13 | val byYearDay: List = emptyList(),
14 | val byHour: List = emptyList()
15 | )
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/dto/KioskMessageDto.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.dto
2 |
3 | import io.swagger.v3.oas.annotations.media.Schema
4 |
5 | /**
6 | * API response for successful operations in notifications module.
7 | */
8 | @Schema(description = "API response for successful operations")
9 | data class KioskMessageDto(
10 | @Schema(
11 | description = "Message for the client",
12 | example = "Kiosk mode enabled for device: 7ac6ddd9a731bbeb"
13 | )
14 | val message: String
15 | )
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/entity/WorkspaceZoneEntity.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.Entity
5 | import jakarta.persistence.Id
6 | import jakarta.persistence.Table
7 | import java.util.UUID
8 |
9 | @Entity
10 | @Table(name = "workspace_zones")
11 | data class WorkspaceZoneEntity(
12 | @Id
13 | var id: UUID,
14 | @Column(name = "name", nullable = false, unique = true, length = 255)
15 | var name: String
16 | )
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/arrow_to_down.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/workspace/UtilityDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto.workspace
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents a utility available in a workspace.
7 | * @property id Unique identifier
8 | * @property name Name of the utility
9 | * @property iconUrl URL to the utility's icon
10 | * @property count Number of this utility available
11 | */
12 | @Serializable
13 | data class UtilityDTO(
14 | val id: String,
15 | val name: String,
16 | val iconUrl: String,
17 | val count: Int
18 | )
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/androidMain/kotlin/band/effective/office/tablet/core/ui/util/DateFormatter.android.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.utils
2 |
3 | import kotlinx.datetime.LocalDateTime
4 | import kotlinx.datetime.toJavaLocalDateTime
5 | import java.time.format.DateTimeFormatter
6 | import java.util.Locale
7 |
8 | actual fun LocalDateTime.toLocalisedString(pattern: String): String {
9 | val formatter = DateTimeFormatter.ofPattern(pattern, Locale.getDefault())
10 | return this.toJavaLocalDateTime().format(formatter)
11 | }
12 |
13 | actual fun getCurrentLanguageCode(): String = Locale.getDefault().language
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/arrow_left.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/arrow_right.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/feature/slot/src/commonMain/kotlin/band/effective/office/tablet/feature/slot/presentation/SlotUi.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.slot.presentation
2 |
3 | import band.effective.office.tablet.core.domain.model.Slot
4 |
5 | sealed interface SlotUi {
6 | val slot: Slot
7 |
8 | data class SimpleSlot(override val slot: Slot) : SlotUi
9 | data class MultiSlot(override val slot: Slot, val subSlots: List, val isOpen: Boolean) :
10 | SlotUi
11 |
12 | data class NestedSlot(override val slot: Slot) : SlotUi
13 | data class LoadingSlot(override val slot: Slot) : SlotUi
14 | }
15 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/arrow_to_right.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/entity/WorkspaceUtilityEntity.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository.entity
2 |
3 | import jakarta.persistence.*
4 |
5 | @Entity
6 | @Table(name = "workspace_utilities")
7 | data class WorkspaceUtilityEntity(
8 | @Id
9 | @ManyToOne
10 | @JoinColumn(name = "workspace_id")
11 | val workspace: WorkspaceEntity,
12 |
13 | @Id
14 | @ManyToOne
15 | @JoinColumn(name = "utility_id")
16 | val utility: UtilityEntity,
17 |
18 | @Column(name = "count", nullable = false)
19 | val count: Int
20 | )
--------------------------------------------------------------------------------
/backend/feature/booking/core/src/main/kotlin/band/effective/office/backend/feature/booking/core/exception/BookingErrorCodes.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.booking.core.exception
2 |
3 | /**
4 | * Error codes for the booking service.
5 | * These constants define the error codes that can be returned by the booking service.
6 | */
7 | object BookingErrorCodes {
8 | // Resource not found errors (1xx)
9 | const val BOOKING_NOT_FOUND = 101
10 | const val USER_NOT_FOUND = 102
11 | const val WORKSPACE_NOT_FOUND = 103
12 |
13 | // Validation errors (2xx)
14 | const val INVALID_TIME_RANGE = 201
15 | const val OVERLAPPING_BOOKING = 202
16 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.client.kmp.ui")
3 | }
4 |
5 | kotlin {
6 | sourceSets {
7 | commonMain.dependencies {
8 | api(libs.kotlinx.datetime)
9 | implementation(libs.decompose)
10 | implementation(libs.decompose.compose.jetbrains)
11 | implementation(libs.essenty.lifecycle)
12 | implementation(libs.essenty.state.keeper)
13 | api(libs.kotlinx.datetime)
14 | }
15 | }
16 | }
17 |
18 | compose.resources {
19 | publicResClass = true
20 | packageOfResClass = "band.effective.office.tablet.core.ui"
21 | generateResClass = auto
22 | }
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/dto/KioskToggleRequest.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.dto
2 |
3 | import io.swagger.v3.oas.annotations.media.Schema
4 |
5 | /**
6 | * Data Transfer Object for kiosk mode toggle requests.
7 | *
8 | * This request allows enabling or disabling kiosk mode for a specific device
9 | */
10 | @Schema(description = "Request to toggle kiosk mode")
11 | data class KioskToggleRequest(
12 | @Schema(
13 | description = "Unique Android device ID",
14 | example = "7ac6ddd9a731bbeb",
15 | required = false
16 | )
17 | val deviceId: String
18 | )
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/common/OrganizerEventView.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.common
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.Composable
6 | import band.effective.office.tablet.core.ui.theme.LocalCustomColorsPalette
7 | import band.effective.office.tablet.core.ui.theme.h5
8 |
9 | @Composable
10 | fun OrganizerEventView(organizer: String) {
11 | Text(
12 | text = organizer,
13 | style = MaterialTheme.typography.h5,
14 | color = LocalCustomColorsPalette.current.primaryTextAndIcon
15 | )
16 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/settings/src/commonMain/kotlin/band/effective/office/tablet/feature/settings/State.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.settings
2 |
3 | import band.effective.office.tablet.core.domain.model.RoomsEnum
4 |
5 | data class State(
6 | val rooms: List,
7 | val currentName: String,
8 | val loading: Boolean,
9 | val error: String,
10 | ) {
11 | companion object {
12 | val defaultState =
13 | State(
14 | rooms = RoomsEnum.entries.map { room -> room.nameRoom }.toList(),
15 | currentName = "",
16 | loading = true,
17 | error = "",
18 | )
19 | }
20 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/failure.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/dto/WorkspaceZoneDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.dto
2 |
3 | import io.swagger.v3.oas.annotations.media.Schema
4 |
5 | /**
6 | * Data Transfer Object for a zone in the office where workspaces are located.
7 | */
8 | @Schema(description = "Zone in the office where workspaces are located")
9 | data class WorkspaceZoneDTO(
10 | @Schema(description = "Unique identifier of the workspace zone", example = "550e8400-e29b-41d4-a716-446655440000")
11 | val id: String,
12 |
13 | @Schema(description = "Name of the workspace zone", example = "Floor 1")
14 | val name: String
15 | )
--------------------------------------------------------------------------------
/backend/app/src/main/kotlin/band/effective/office/backend/app/EffectiveOfficeApplication.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.app
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication
4 | import org.springframework.boot.runApplication
5 | import org.springframework.context.annotation.ComponentScan
6 |
7 | /**
8 | * Main application class for the Effective Office application.
9 | */
10 | @SpringBootApplication
11 | @ComponentScan(basePackages = ["band.effective.office.backend"])
12 | class EffectiveOfficeApplication
13 |
14 | /**
15 | * Main function that starts the Spring Boot application.
16 | */
17 | fun main(args: Array) {
18 | runApplication(*args)
19 | }
--------------------------------------------------------------------------------
/backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/exception/AuthorizationErrorCodes.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.authorization.exception
2 |
3 | /**
4 | * Error codes for the authorization service.
5 | * These constants define the error codes that can be returned by the authorization service.
6 | */
7 | object AuthorizationErrorCodes {
8 | // Authentication errors (3xx)
9 | const val INVALID_TOKEN = 301
10 | const val EXPIRED_TOKEN = 302
11 | const val MISSING_TOKEN = 303
12 | const val INVALID_API_KEY = 304
13 | const val UNAUTHORIZED = 305
14 |
15 | // Server errors (4xx)
16 | const val AUTHORIZATION_SERVER_ERROR = 401
17 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/Either.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain
2 |
3 | /**
4 | * Represents a value of one of two possible types (a disjoint union).
5 | * Instances of [Either] are either an instance of [Error] or [Success].
6 | */
7 | sealed interface Either {
8 | /**
9 | * Represents the error case of the disjoint union.
10 | */
11 | data class Error(val error: ErrorType) : Either
12 |
13 | /**
14 | * Represents the success case of the disjoint union.
15 | */
16 | data class Success(val data: DataType) : Either
17 | }
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/config/WorkspaceConfig.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.config
2 |
3 | import org.springframework.boot.autoconfigure.domain.EntityScan
4 | import org.springframework.context.annotation.Configuration
5 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories
6 | import org.springframework.transaction.annotation.EnableTransactionManagement
7 |
8 | @Configuration
9 | @EnableTransactionManagement
10 | @EnableJpaRepositories("band.effective.office.backend.feature.workspace.core.repository")
11 | @EntityScan("band.effective.office.backend.feature.workspace.core.repository.entity")
12 | class WorkspaceConfig
--------------------------------------------------------------------------------
/backend/core/domain/src/main/kotlin/band/effective/office/backend/core/domain/model/Workspace.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.core.domain.model
2 |
3 | import java.util.UUID
4 |
5 | /**
6 | * Domain model representing a workspace in the office.
7 | *
8 | * @property id The unique identifier for the workspace
9 | * @property name The name of the workspace
10 | * @property tag The tag for categorizing the workspace
11 | * @property utilities List of utilities available in the workspace
12 | * @property zone The zone where the workspace is located
13 | */
14 | data class Workspace(
15 | val id: UUID,
16 | val name: String,
17 | val tag: String,
18 | val utilities: List,
19 | val zone: WorkspaceZone? = null
20 | )
--------------------------------------------------------------------------------
/backend/feature/booking/calendar/google/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | implementation(project(":backend:feature:booking:core"))
7 |
8 | implementation(libs.jakarta)
9 | implementation(libs.jakarta.servlet.api)
10 |
11 | implementation(libs.springdoc.openapi.starter.webmvc.ui)
12 |
13 | // Google Calendar API
14 | implementation("com.google.api-client:google-api-client:2.2.0")
15 | implementation("com.google.oauth-client:google-oauth-client-jetty:1.34.1")
16 | implementation("com.google.apis:google-api-services-calendar:v3-rev20230707-2.0.0")
17 | implementation("com.google.auth:google-auth-library-oauth2-http:1.3.0")
18 | }
19 |
--------------------------------------------------------------------------------
/clients/tablet/feature/bookingEditor/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Book %1$s
4 | Edit booking
5 | Book from %1$s to %2$s
6 | Confirm changes
7 | Delete booking
8 | An error occurred
9 | Error, incorrect date selected
10 | Error creating event
11 | Error deleting event
12 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/repository/OrganizerRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.repository
2 |
3 | import band.effective.office.tablet.core.domain.Either
4 | import band.effective.office.tablet.core.domain.ErrorWithData
5 | import band.effective.office.tablet.core.domain.model.Organizer
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | /**Repository for get info about organizers*/
10 | interface OrganizerRepository {
11 | suspend fun getOrganizersList(): Either>, List>
12 | fun subscribeOnUpdates(scope: CoroutineScope): Flow>, List>>
13 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/cross.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/repository/DeviceRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.repository
2 |
3 | import band.effective.office.backend.feature.notifications.repository.entity.DeviceEntity
4 | import org.springframework.data.jpa.repository.JpaRepository
5 | import org.springframework.stereotype.Repository
6 | import java.util.UUID
7 |
8 | /**
9 | * Repository for working with devices
10 | */
11 | @Repository
12 | interface DeviceRepository : JpaRepository {
13 |
14 | /**
15 | * Check if device exists by device_id
16 | * @return true if device exists, false otherwise
17 | */
18 | fun existsByDeviceId(deviceId: String): Boolean
19 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/RootHolder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootHolder.swift
3 | // iosApp
4 | //
5 | // Created by Stanislav Radchenko on 04.07.2025.
6 | //
7 | import ComposeApp
8 | import Foundation
9 | import SwiftUI
10 |
11 | class RootHolder : ObservableObject {
12 | let lifecycle: LifecycleRegistry
13 | let root: RootComponent
14 |
15 | init() {
16 | lifecycle = LifecycleRegistryKt.LifecycleRegistry()
17 |
18 | root = RootComponent(
19 | componentContext: DefaultComponentContext(lifecycle: lifecycle)
20 | )
21 |
22 | LifecycleRegistryExtKt.create(lifecycle)
23 | }
24 |
25 | deinit {
26 | // Destroy the root component before it is deallocated
27 | LifecycleRegistryExtKt.destroy(lifecycle)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/utils/Buffer.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.utils
2 |
3 | import band.effective.office.tablet.core.domain.Either
4 | import band.effective.office.tablet.core.domain.ErrorResponse
5 | import band.effective.office.tablet.core.domain.ErrorWithData
6 | import band.effective.office.tablet.core.domain.model.RoomInfo
7 | import kotlinx.coroutines.flow.MutableStateFlow
8 |
9 | class Buffer {
10 | val state = MutableStateFlow>, List>>(
11 | Either.Error(
12 | ErrorWithData(
13 | error = ErrorResponse.getResponse(400),
14 | saveData = emptyList()
15 | )
16 | )
17 | )
18 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/bookingEditor/src/commonMain/composeResources/values-ru/strings_ru.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Занять %1$s
4 | Изменить бронь
5 | Занять c %1$s до %2$s
6 | Сохранить изменения
7 | Удалить бронь
8 | Произошла ошибка
9 | Ошибка, выбрана неправильная дата
10 | Ошибка при создании события
11 | Ошибка при удалении события
12 |
13 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/kotlin/band/effective/office/tablet/App.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet
2 |
3 | import androidx.compose.runtime.Composable
4 | import band.effective.office.tablet.core.ui.theme.AppTheme
5 | import band.effective.office.tablet.root.Root
6 | import band.effective.office.tablet.root.RootComponent
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.ui.Modifier
10 | import band.effective.office.tablet.components.VersionOverlay
11 |
12 | @Composable
13 | fun App(rootComponent: RootComponent) {
14 | AppTheme {
15 | Box(modifier = Modifier.fillMaxSize()) {
16 | Root(rootComponent)
17 | VersionOverlay()
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/backend/core/repository/src/main/kotlin/band/effective/office/backend/core/repository/config/DatabaseConfig.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.core.repository.config
2 |
3 | import org.springframework.boot.autoconfigure.domain.EntityScan
4 | import org.springframework.context.annotation.Configuration
5 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories
6 | import org.springframework.transaction.annotation.EnableTransactionManagement
7 |
8 | /**
9 | * Configuration for the database and JPA repositories.
10 | */
11 | @Configuration
12 | @EnableTransactionManagement
13 | @EnableJpaRepositories(basePackages = ["band.effective.office.backend.core.repository"])
14 | @EntityScan(basePackages = ["band.effective.office.backend.repository.entity"])
15 | class DatabaseConfig
16 |
--------------------------------------------------------------------------------
/backend/feature/user/src/main/kotlin/band/effective/office/backend/feature/user/config/UserRepositoryConfig.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.user.config
2 |
3 | import org.springframework.boot.autoconfigure.domain.EntityScan
4 | import org.springframework.context.annotation.Configuration
5 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories
6 | import org.springframework.transaction.annotation.EnableTransactionManagement
7 |
8 | /**
9 | * Configuration for the user repository.
10 | */
11 | @Configuration
12 | @EnableTransactionManagement
13 | @EnableJpaRepositories(basePackages = ["band.effective.office.backend.feature.user.repository"])
14 | @EntityScan(basePackages = ["band.effective.office.backend.feature.user.repository.entity"])
15 | class UserRepositoryConfig
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/LoadMainScreen.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import band.effective.office.tablet.core.ui.common.Loader
11 |
12 | @Composable
13 | fun LoadMainScreen() {
14 | Box(
15 | modifier = Modifier.background(color = MaterialTheme.colorScheme.background).fillMaxSize(),
16 | contentAlignment = Alignment.Center
17 | ) {
18 | Loader()
19 | }
20 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/OfficeTime.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain
2 |
3 | import band.effective.office.tablet.core.domain.util.currentLocalDateTime
4 | import kotlinx.datetime.LocalDate
5 | import kotlinx.datetime.LocalDateTime
6 | import kotlinx.datetime.LocalTime
7 |
8 | object OfficeTime {
9 |
10 | private val startWorkLocalTime = LocalTime(8, 0)
11 | private val endWorkLocalTime = LocalTime(22, 0)
12 |
13 | fun startWorkTime(localDate: LocalDate = currentLocalDateTime.date): LocalDateTime =
14 | LocalDateTime(localDate, startWorkLocalTime)
15 |
16 | fun finishWorkTime(localDate: LocalDate = currentLocalDateTime.date): LocalDateTime =
17 | LocalDateTime(localDate, endWorkLocalTime)
18 |
19 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/composeResources/drawable/ic_rotate_right.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
--------------------------------------------------------------------------------
/backend/feature/notifications/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("band.effective.office.backend.spring-data-jpa")
3 | }
4 |
5 | dependencies {
6 | implementation(libs.jakarta)
7 | implementation(libs.jakarta.servlet.api)
8 | implementation(libs.springdoc.openapi.starter.webmvc.ui)
9 |
10 | // Firebase dependencies
11 | implementation("com.google.firebase:firebase-admin:9.2.0")
12 |
13 | // Jackson dependencies
14 | implementation(libs.jackson.module.kotlin)
15 | implementation(libs.jackson.datatype.jsr310)
16 |
17 | implementation("com.google.apis:google-api-services-calendar:v3-rev411-1.25.0")
18 |
19 | // Project dependencies
20 | implementation(project(":backend:feature:booking:core"))
21 | implementation(project(":backend:feature:calendar-subscription"))
22 | }
23 |
--------------------------------------------------------------------------------
/backend/core/domain/src/main/kotlin/band/effective/office/backend/core/domain/model/CalendarId.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.core.domain.model
2 |
3 | import java.time.LocalDateTime
4 | import java.util.UUID
5 |
6 | /**
7 | * Domain model representing a calendar ID linked to a workspace.
8 | *
9 | * @property id The unique identifier for the calendar ID
10 | * @property workspaceId The ID of the workspace this calendar ID is linked to
11 | * @property calendarId The Google Calendar ID
12 | * @property createdAt When this calendar ID was created
13 | * @property updatedAt When this calendar ID was last updated
14 | */
15 | data class CalendarId(
16 | val id: UUID,
17 | val workspaceId: UUID,
18 | val calendarId: String,
19 | val createdAt: LocalDateTime,
20 | val updatedAt: LocalDateTime
21 | )
--------------------------------------------------------------------------------
/clients/tablet/feature/slot/src/commonMain/kotlin/band/effective/office/tablet/feature/slot/presentation/mapper/SlotUiMapper.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.slot.presentation.mapper
2 |
3 | import band.effective.office.tablet.core.domain.model.Slot
4 | import band.effective.office.tablet.feature.slot.presentation.SlotUi
5 |
6 | class SlotUiMapper {
7 |
8 | fun map(slot: Slot): SlotUi = when (slot) {
9 | is Slot.EmptySlot -> SlotUi.SimpleSlot(slot)
10 | is Slot.EventSlot -> SlotUi.SimpleSlot(slot)
11 | is Slot.MultiEventSlot -> SlotUi.MultiSlot(
12 | slot = slot,
13 | subSlots = slot.events.map { slot -> SlotUi.NestedSlot(slot) },
14 | isOpen = false
15 | )
16 |
17 | is Slot.LoadingEventSlot -> SlotUi.LoadingSlot(slot)
18 | }
19 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/GetTimeToNextEventUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.domain
2 |
3 | import band.effective.office.tablet.core.domain.model.RoomInfo
4 | import kotlinx.datetime.Clock
5 | import kotlinx.datetime.TimeZone
6 | import kotlinx.datetime.toInstant
7 |
8 | class GetTimeToNextEventUseCase {
9 |
10 | operator fun invoke(rooms: List, selectedRoomIndex: Int): Int {
11 | val now = Clock.System.now()
12 | val timeZone = TimeZone.currentSystemDefault()
13 | val currentEvent = rooms.getOrNull(selectedRoomIndex)?.currentEvent ?: return 0
14 | val finishInstant = currentEvent.finishTime.toInstant(timeZone)
15 |
16 | return ((finishInstant - now).inWholeMinutes).toInt()
17 | }
18 | }
--------------------------------------------------------------------------------
/backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/apikey/repository/ApiKeyRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.authorization.apikey.repository
2 |
3 | import band.effective.office.backend.feature.authorization.apikey.entity.ApiKey
4 | import org.springframework.data.jpa.repository.JpaRepository
5 | import org.springframework.stereotype.Repository
6 | import java.util.UUID
7 |
8 | /**
9 | * Repository for accessing API keys stored in the database.
10 | */
11 | @Repository
12 | interface ApiKeyRepository : JpaRepository {
13 |
14 | /**
15 | * Finds an API key by its hashed value.
16 | *
17 | * @param keyValue The hashed API key value
18 | * @return The API key, or null if not found
19 | */
20 | fun findByKeyValue(keyValue: String): ApiKey?
21 | }
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/mapper/RoomInfoMapper.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.mapper
2 |
3 | import band.effective.office.tablet.core.data.dto.workspace.WorkspaceDTO
4 | import band.effective.office.tablet.core.domain.model.RoomInfo
5 |
6 | class RoomInfoMapper(
7 | private val eventInfoMapper: EventInfoMapper,
8 | ) {
9 |
10 | fun map(dto: WorkspaceDTO) = RoomInfo(
11 | name = dto.name,
12 | capacity = dto.utilities.firstOrNull { it.name == "place" }?.count ?: 0,
13 | isHaveTv = dto.utilities.any { it.name == "tv" },
14 | socketCount = dto.utilities.firstOrNull { it.name == "lan" }?.count ?: 0,
15 | eventList = dto.bookings?.map(eventInfoMapper::map) ?: emptyList(),
16 | currentEvent = null,
17 | id = dto.id
18 | )
19 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/utils/componentCoroutineScope.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.utils
2 |
3 | import com.arkivanov.decompose.ComponentContext
4 | import com.arkivanov.essenty.lifecycle.Lifecycle
5 | import com.arkivanov.essenty.lifecycle.doOnDestroy
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.SupervisorJob
9 | import kotlinx.coroutines.cancel
10 |
11 | fun ComponentContext.componentCoroutineScope(): CoroutineScope {
12 | val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
13 |
14 | if (lifecycle.state != Lifecycle.State.DESTROYED) {
15 | lifecycle.doOnDestroy {
16 | scope.cancel()
17 | }
18 | } else {
19 | scope.cancel()
20 | }
21 |
22 | return scope
23 | }
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/config/NotificationsRepositoryConfig.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.config
2 |
3 | import org.springframework.boot.autoconfigure.domain.EntityScan
4 | import org.springframework.context.annotation.Configuration
5 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories
6 | import org.springframework.transaction.annotation.EnableTransactionManagement
7 |
8 | /**
9 | * Configuration for the notifications repository.
10 | */
11 | @Configuration
12 | @EnableTransactionManagement
13 | @EnableJpaRepositories(basePackages = ["band.effective.office.backend.feature.notifications.repository"])
14 | @EntityScan(basePackages = ["band.effective.office.backend.feature.notifications.repository.entity"])
15 | class NotificationsRepositoryConfig
--------------------------------------------------------------------------------
/backend/core/repository/src/main/resources/application-repository.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | url: ${SPRING_DATASOURCE_URL}
4 | username: ${SPRING_DATASOURCE_USERNAME}
5 | password: ${SPRING_DATASOURCE_PASSWORD}
6 | driver-class-name: org.postgresql.Driver
7 | hikari:
8 | connection-timeout: 30000
9 | maximum-pool-size: 10
10 | minimum-idle: 5
11 | pool-name: EffectiveOfficeHikariCP
12 |
13 | jpa:
14 | hibernate:
15 | ddl-auto: validate
16 | properties:
17 | hibernate:
18 | dialect: org.hibernate.dialect.PostgreSQLDialect
19 | format_sql: true
20 | show_sql: false
21 | use_sql_comments: true
22 | open-in-view: false
23 |
24 | flyway:
25 | enabled: true
26 | baseline-on-migrate: true
27 | locations: classpath:db/migration
28 | table: flyway_schema_history
29 |
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/entity/UtilityEntity.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.OneToMany
5 | import jakarta.persistence.Entity
6 | import jakarta.persistence.Id
7 | import jakarta.persistence.Table
8 | import java.util.UUID
9 |
10 | @Entity
11 | @Table(name = "utilities")
12 | data class UtilityEntity(
13 | @Id
14 | val id: UUID,
15 | @Column(name = "name", nullable = false, unique = true, length = 255)
16 | val name: String,
17 | @Column(name = "icon_url", nullable = false, unique = true, length = 255)
18 | val iconUrl: String,
19 |
20 | @OneToMany(mappedBy = "utility")
21 | val workspaceUtilities: List = emptyList()
22 | )
23 |
--------------------------------------------------------------------------------
/backend/feature/calendar-subscription/src/main/kotlin/band/effective/office/backend/feature/calendar/subscription/config/ChannelRepositoryConfig.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.calendar.subscription.config
2 |
3 | import org.springframework.boot.autoconfigure.domain.EntityScan
4 | import org.springframework.context.annotation.Configuration
5 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories
6 | import org.springframework.transaction.annotation.EnableTransactionManagement
7 |
8 | /**
9 | * Configuration for the calendar channel repository.
10 | */
11 | @Configuration
12 | @EnableTransactionManagement
13 | @EnableJpaRepositories(basePackages = ["band.effective.office.backend.feature.calendar.subscription.repository"])
14 | @EntityScan(basePackages = ["band.effective.office.backend.feature.calendar.subscription.repository.entity"])
15 | class ChannelRepositoryConfig
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/dto/UtilityDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.dto
2 |
3 | import io.swagger.v3.oas.annotations.media.Schema
4 |
5 | /**
6 | * Data Transfer Object for a utility available in a workspace.
7 | */
8 | @Schema(description = "Utility available in a workspace")
9 | data class UtilityDTO(
10 | @Schema(description = "Unique identifier of the utility", example = "550e8400-e29b-41d4-a716-446655440000")
11 | val id: String,
12 |
13 | @Schema(description = "Name of the utility", example = "Monitor")
14 | val name: String,
15 |
16 | @Schema(description = "URL for the utility's icon", example = "https://example.com/icons/monitor.png")
17 | val iconUrl: String,
18 |
19 | @Schema(description = "Quantity of this utility", example = "2")
20 | val count: Int
21 | )
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/presentation/main/navigation/ModalWindowsConfig.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.presentation.main.navigation
2 |
3 | import band.effective.office.tablet.core.domain.model.EventInfo
4 | import band.effective.office.tablet.core.domain.model.RoomInfo
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | sealed interface ModalWindowsConfig {
9 |
10 | @Serializable
11 | data class UpdateEvent(
12 | val event: EventInfo,
13 | val room: String
14 | ) : ModalWindowsConfig
15 |
16 | @Serializable
17 | data class FreeRoom(val event: EventInfo) : ModalWindowsConfig
18 |
19 | @Serializable
20 | data class FastEvent(
21 | val minEventDuration: Int,
22 | val selectedRoom: RoomInfo,
23 | val rooms: List
24 | ) : ModalWindowsConfig
25 | }
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/repository/entity/DeviceEntity.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.repository.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.Entity
5 | import jakarta.persistence.Id
6 | import jakarta.persistence.Table
7 | import java.time.LocalDateTime
8 | import java.util.UUID
9 |
10 | /**
11 | * JPA entity for devices with kiosk mode functionality.
12 | */
13 | @Entity
14 | @Table(name = "devices")
15 | class DeviceEntity(
16 | @Id
17 | val id: UUID = UUID.randomUUID(),
18 |
19 | @Column(name = "device_id", nullable = false, unique = true, length = 255)
20 | val deviceId: String,
21 |
22 | @Column(nullable = false, length = 255)
23 | val tag: String,
24 |
25 | @Column(name = "created_at", nullable = false)
26 | val createdAt: LocalDateTime = LocalDateTime.now()
27 | )
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/kotlin/band/effective/office/tablet/di/KoinInitializer.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.di
2 |
3 | import band.effective.office.tablet.core.data.di.dataModule
4 | import band.effective.office.tablet.core.domain.di.domainModule
5 | import band.effective.office.tablet.feature.bookingEditor.di.bookingEditorModule
6 | import band.effective.office.tablet.feature.main.di.mainScreenModule
7 | import band.effective.office.tablet.feature.slot.di.slotDiModule
8 | import org.koin.core.context.startKoin
9 |
10 | class KoinInitializer {
11 | fun init() {
12 | startKoin {
13 | modules(
14 | appModule,
15 | firebaseTopicsModule,
16 | dataModule,
17 | domainModule,
18 | mainScreenModule,
19 | bookingEditorModule,
20 | slotDiModule,
21 | )
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/booking/BookingRequestDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto.booking
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents a request to book a workspace.
7 | * @property ownerEmail Email of the booking owner
8 | * @property participantEmails List of participant emails
9 | * @property workspaceId ID of the workspace to book
10 | * @property beginBooking Start time of the booking (Unix timestamp)
11 | * @property endBooking End time of the booking (Unix timestamp)
12 | * @property recurrence Recurrence pattern for recurring bookings
13 | */
14 | @Serializable
15 | data class BookingRequestDTO(
16 | val ownerEmail: String?,
17 | val participantEmails: List,
18 | val workspaceId: String,
19 | val beginBooking: Long,
20 | val endBooking: Long,
21 | val recurrence: RecurrenceDTO? = null
22 | )
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/TimerUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import kotlin.time.Duration
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.IO
7 | import kotlinx.coroutines.delay
8 | import kotlinx.coroutines.flow.flow
9 | import kotlinx.coroutines.launch
10 |
11 | /**Custom timers*/
12 | class TimerUseCase {
13 | fun timerFlow(delay: Duration) = flow {
14 | var i = 0L
15 | while (true) {
16 | delay(delay)
17 | emit(value = i++)
18 | }
19 | }
20 |
21 | fun timer(
22 | scope: CoroutineScope,
23 | delay: Duration,
24 | onTick: suspend CoroutineScope.(Long) -> Unit
25 | ) =
26 | scope.launch(Dispatchers.IO) {
27 | timerFlow(delay).collect { onTick(it) }
28 | }
29 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/bookingEditor/src/commonMain/kotlin/band/effective/office/tablet/feature/bookingEditor/presentation/mapper/UpdateEventComponentStateToEventInfoMapper.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.bookingEditor.presentation.mapper
2 |
3 | import band.effective.office.tablet.core.domain.model.EventInfo
4 | import band.effective.office.tablet.core.domain.util.asInstant
5 | import band.effective.office.tablet.core.domain.util.asLocalDateTime
6 | import band.effective.office.tablet.feature.bookingEditor.presentation.State
7 | import kotlin.time.Duration.Companion.minutes
8 |
9 | class UpdateEventComponentStateToEventInfoMapper {
10 |
11 | fun map(state: State): EventInfo = EventInfo(
12 | startTime = state.date,
13 | finishTime = state.date.asInstant.plus(state.duration.minutes).asLocalDateTime,
14 | organizer = state.selectOrganizer,
15 | id = state.event.id,
16 | isLoading = false,
17 | )
18 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/utils/KioskCommandBus.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.utils
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.flow.MutableSharedFlow
5 | import kotlinx.coroutines.flow.asSharedFlow
6 | import kotlinx.coroutines.launch
7 |
8 | class KioskCommandBus {
9 | private val _commandFlow = MutableSharedFlow()
10 | val commandFlow = _commandFlow.asSharedFlow()
11 |
12 | fun sendCommand(command: KioskCommand, scope: CoroutineScope) {
13 | scope.launch {
14 | _commandFlow.emit(command)
15 | }
16 | }
17 |
18 | companion object {
19 | private var instance: KioskCommandBus? = null
20 |
21 | fun getInstance(): KioskCommandBus {
22 | return instance ?: synchronized(this) {
23 | instance ?: KioskCommandBus().also { instance = it }
24 | }
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/workspace/WorkspaceDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto.workspace
2 |
3 | import band.effective.office.tablet.core.data.dto.booking.BookingResponseDTO
4 | import kotlinx.serialization.Serializable
5 |
6 | /**
7 | * Represents a workspace.
8 | * @property id Unique identifier
9 | * @property name Name of the workspace
10 | * @property utilities List of utilities available in the workspace
11 | * @property zone Zone where the workspace is located
12 | * @property tag Type of workspace (e.g., meeting, regular)
13 | * @property bookings List of bookings for this workspace (optional)
14 | */
15 | @Serializable
16 | data class WorkspaceDTO(
17 | val id: String,
18 | val name: String,
19 | val utilities: List,
20 | val zone: WorkspaceZoneDTO? = null,
21 | val tag: String,
22 | val bookings: List? = null
23 | )
24 |
--------------------------------------------------------------------------------
/.aiignore:
--------------------------------------------------------------------------------
1 | # An .aiignore file follows the same syntax as a .gitignore file.
2 | # .gitignore documentation: https://git-scm.com/docs/gitignore
3 | # Junie will ask for explicit approval before view or edit the file or file within a directory listed in .aiignore.
4 | # Only files contents is protected, Junie is still allowed to view file names even if they are listed in .aiignore.
5 | # Be aware that the files you included in .aiignore can still be accessed by Junie in two cases:
6 | # - If Brave Mode is turned on.
7 | # - If a command has been added to the Allowlist — Junie will not ask for confirmation, even if it accesses - files and folders listed in .aiignore.
8 | backend/app/src/main/resources/.env
9 | backend/app/src/main/resources/firebase-credentials.json
10 | backend/app/src/main/resources/google-credentials.json
11 | deploy/dev/.env
12 | deploy/prod/.env
13 | deploy/dev/*.jar
14 | deploy/prod/*.jar
15 | keystore
16 | local.properties
17 | clients/tablet/composeApp/google-services.json
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/mapper/CalendarIdMapper.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository.mapper
2 |
3 | import band.effective.office.backend.core.domain.model.CalendarId
4 | import band.effective.office.backend.feature.workspace.core.repository.entity.CalendarIdEntity
5 |
6 | /**
7 | * Mapper for converting between CalendarIdEntity and CalendarId domain model.
8 | */
9 | object CalendarIdMapper {
10 | /**
11 | * Convert a CalendarIdEntity to a CalendarId domain model.
12 | *
13 | * @param entity The entity to convert
14 | * @return The domain model
15 | */
16 | fun toDomain(entity: CalendarIdEntity): CalendarId = CalendarId(
17 | id = entity.id,
18 | workspaceId = entity.workspace.id,
19 | calendarId = entity.calendarId,
20 | createdAt = entity.createdAt,
21 | updatedAt = entity.updatedAt
22 | )
23 | }
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/user/UserDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto.user
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents a user in the system.
7 | * @property id Unique identifier (UUIDv4)
8 | * @property fullName Full name of the user
9 | * @property active Whether the user is active
10 | * @property role Role of the user
11 | * @property avatarUrl URL to the user's avatar
12 | * @property integrations List of integrations associated with the user
13 | * @property email Email address of the user
14 | * @property tag User type (employee or guest)
15 | */
16 | @Serializable
17 | data class UserDTO(
18 | val id: String,
19 | val fullName: String,
20 | val active: Boolean,
21 | val role: String,
22 | val avatarUrl: String,
23 | val integrations: List? = null,
24 | val email: String = "",
25 | val tag: String = ""
26 | )
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/ErrorResponse.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain
2 |
3 | /**
4 | * Represents an error response.
5 | * @property code Error code
6 | * @property description Error description
7 | */
8 | data class ErrorResponse(val code: Int, val description: String) {
9 | companion object {
10 | /**
11 | * Creates an error response based on the error code.
12 | * @param code Error code
13 | * @return ErrorResponse with a description based on the code
14 | */
15 | fun getResponse(code: Int): ErrorResponse {
16 | val description = when (code) {
17 | 404 -> "Not found"
18 | in 400..499 -> "Client error"
19 | in 500..599 -> "Server error"
20 | else -> "Unknown error"
21 | }
22 | return ErrorResponse(code, description)
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/RoomInfo.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Domain model representing a room.
7 | */
8 | @Serializable
9 | data class RoomInfo(
10 | val name: String,
11 | val capacity: Int,
12 | val isHaveTv: Boolean,
13 | val socketCount: Int,
14 | val eventList: List,
15 | val currentEvent: EventInfo?, //NOTE(Maksim Mishenko): currentEvent is null if room is free
16 | val id: String
17 | ) {
18 | companion object {
19 | val defaultValue =
20 | RoomInfo(
21 | name = "Default",
22 | capacity = 0,
23 | isHaveTv = false,
24 | socketCount = 0,
25 | eventList = listOf(),
26 | currentEvent = null,
27 | id = ""
28 | )
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/clients/tablet/feature/settings/src/commonMain/kotlin/band/effective/office/tablet/feature/settings/components/TitleView.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.settings.components
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import band.effective.office.settings.generated.resources.Res
8 | import band.effective.office.settings.generated.resources.rooms
9 | import band.effective.office.tablet.core.ui.theme.LocalCustomColorsPalette
10 | import band.effective.office.tablet.core.ui.theme.h2
11 | import org.jetbrains.compose.resources.stringResource
12 |
13 | @Composable
14 | fun TitleView(modifier: Modifier) {
15 | Text(
16 | modifier = modifier,
17 | text = stringResource(Res.string.rooms),
18 | style = MaterialTheme.typography.h2,
19 | color = LocalCustomColorsPalette.current.primaryTextAndIcon
20 | )
21 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/utils/MessageValidator.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.utils
2 |
3 | import com.google.firebase.messaging.RemoteMessage
4 |
5 | /**
6 | * Validator for checking the integrity of incoming messages.
7 | */
8 | object MessageValidator {
9 | /**
10 | * Checks if the message contains valid kiosk command data.
11 | */
12 | fun isValidKioskCommand(message: RemoteMessage): Boolean {
13 | return message.data.containsKey("isKioskModeActive") &&
14 | (message.data.containsKey("deviceId") || message.data.containsKey("deviceIds"))
15 | }
16 |
17 | /**
18 | * Validates and converts the kiosk mode value from a string to a boolean.
19 | */
20 | fun validateKioskModeValue(value: String?): Boolean? {
21 | return try {
22 | value?.toBooleanStrictOrNull()
23 | } catch (e: IllegalArgumentException) {
24 | null
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/iosMain/kotlin/band/effective/office/tablet/core/ui/utils/DateFormatter.ios.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.utils
2 |
3 | import kotlinx.datetime.LocalDateTime
4 | import platform.Foundation.NSDate
5 | import platform.Foundation.NSDateFormatter
6 | import platform.Foundation.NSLocale
7 | import platform.Foundation.currentLocale
8 |
9 |
10 | import kotlinx.datetime.toNSDateComponents
11 | import platform.Foundation.*
12 |
13 | actual fun LocalDateTime.toLocalisedString(pattern: String): String {
14 | val dateFormatter = NSDateFormatter()
15 | dateFormatter.dateFormat = pattern
16 | dateFormatter.locale = NSLocale.currentLocale
17 |
18 | val calendar = NSCalendar.currentCalendar
19 | val dateComponents = toNSDateComponents()
20 |
21 | val date = calendar.dateFromComponents(dateComponents) ?: NSDate()
22 | return dateFormatter.stringFromDate(date)
23 | }
24 |
25 | actual fun getCurrentLanguageCode(): String = NSLocale.currentLocale.languageCode
--------------------------------------------------------------------------------
/backend/core/repository/src/main/resources/db/migration/V3__add_devices_table.sql:
--------------------------------------------------------------------------------
1 | -- Creates the table for storing device information
2 | CREATE TABLE devices
3 | (
4 | -- Primary key
5 | id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6 | -- Unique Android device ID
7 | device_id VARCHAR(255) NOT NULL UNIQUE,
8 | -- Tag for device identification (e.g., meeting room name)
9 | tag VARCHAR(255) NOT NULL,
10 | -- Creation timestamp of the record
11 | created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
12 | );
13 |
14 | -- Index for faster lookup by device_id
15 | CREATE INDEX idx_devices_device_id ON devices (device_id);
16 |
17 | -- Index for faster lookup by tag
18 | CREATE INDEX idx_devices_tag ON devices (tag);
19 |
20 | -- Comments for table and columns
21 | COMMENT ON TABLE devices IS 'Table for storing information about devices';
22 | COMMENT ON COLUMN devices.device_id IS 'Unique Android device ID';
23 | COMMENT ON COLUMN devices.tag IS 'Tag for device identification';
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/GetRoomNamesUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import band.effective.office.tablet.core.domain.model.RoomInfo
4 | import band.effective.office.tablet.core.domain.unbox
5 |
6 | /**
7 | * Use case for getting the names of all available rooms.
8 | *
9 | * @property getRoomsInfoUseCase Use case for getting information about all rooms
10 | */
11 | class GetRoomNamesUseCase(
12 | private val getRoomsInfoUseCase: GetRoomsInfoUseCase,
13 | ) {
14 | /**
15 | * Gets the names of all available rooms.
16 | * If no rooms are available, returns a list with the default room name.
17 | *
18 | * @return List of room names
19 | */
20 | suspend operator fun invoke(): List {
21 | val rooms = getRoomsInfoUseCase().unbox(
22 | errorHandler = { it.saveData }
23 | )
24 | return rooms?.map { it.name } ?: listOf(RoomInfo.defaultValue.name)
25 | }
26 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/bookingEditor/src/commonMain/kotlin/band/effective/office/tablet/feature/bookingEditor/presentation/Intent.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.bookingEditor.presentation
2 |
3 | import band.effective.office.tablet.core.domain.model.Organizer
4 | import kotlinx.datetime.LocalDateTime
5 |
6 | sealed interface Intent {
7 | data class OnUpdateLength(val update: Int) : Intent
8 | data class OnUpdateDate(val updateInDays: Int) : Intent
9 | data class OnSetDate(val calendar: LocalDateTime) : Intent
10 |
11 | data object OnExpandedChange : Intent
12 | data class OnSelectOrganizer(val newOrganizer: Organizer) : Intent
13 | data class OnUpdateEvent(val room: String) : Intent
14 | data object OnDeleteEvent : Intent
15 | data class OnInput(val input: String) : Intent
16 | data object OnDoneInput : Intent
17 | data object OnOpenSelectDateDialog : Intent
18 | data object OnCloseSelectDateDialog : Intent
19 | data object OnClose : Intent
20 | data object OnBooking : Intent
21 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/fastbooking/src/commonMain/kotlin/band/effective/office/tablet/feature/fastBooking/presentation/State.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.fastBooking.presentation
2 |
3 | import band.effective.office.tablet.core.domain.model.EventInfo
4 | import band.effective.office.tablet.core.domain.util.currentLocalDateTime
5 | import kotlinx.datetime.LocalDateTime
6 |
7 | /**
8 | * State for the FastBookingComponent.
9 | */
10 | data class State(
11 | val isLoad: Boolean,
12 | val isSuccess: Boolean,
13 | val isError: Boolean,
14 | val event: EventInfo,
15 | val minutesLeft: Int,
16 | val currentTime: LocalDateTime,
17 | ) {
18 | companion object {
19 | val defaultState =
20 | State(
21 | isLoad = true,
22 | isSuccess = false,
23 | isError = false,
24 | event = EventInfo.emptyEvent,
25 | minutesLeft = 0,
26 | currentTime = currentLocalDateTime,
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/composeResources/drawable/ic_cyclone.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
--------------------------------------------------------------------------------
/backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/apikey/entity/ApiKey.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.authorization.apikey.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.Entity
5 | import jakarta.persistence.GeneratedValue
6 | import jakarta.persistence.GenerationType
7 | import jakarta.persistence.Id
8 | import jakarta.persistence.Table
9 | import java.util.UUID
10 |
11 | /**
12 | * Entity representing an API key stored in the database.
13 | * The key value is stored as a hashed string for security.
14 | */
15 | @Entity
16 | @Table(name = "api_keys")
17 | data class ApiKey(
18 | @Id
19 | @GeneratedValue(strategy = GenerationType.UUID)
20 | val id: UUID = UUID.randomUUID(),
21 |
22 | @Column(name = "key_value", nullable = false, unique = true)
23 | val keyValue: String,
24 |
25 | @Column(name = "description")
26 | val description: String? = null,
27 |
28 | @Column(name = "user_id")
29 | val userId: UUID? = null
30 | )
--------------------------------------------------------------------------------
/iosApp/iosApp/iosApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import ComposeApp
3 | import Foundation
4 | import SwiftUI
5 |
6 | @main
7 | struct iOSApp: App {
8 | @UIApplicationDelegateAdaptor(AppDelegate.self)
9 | var appDelegate: AppDelegate
10 |
11 | @Environment(\.scenePhase)
12 | var scenePhase: ScenePhase
13 |
14 | var rootHolder: RootHolder { appDelegate.rootHolder }
15 |
16 | init() {
17 | Initializers().doInit()
18 | }
19 |
20 | var body: some Scene {
21 | WindowGroup {
22 | RootView(root: rootHolder.root)
23 | .onChange(of: scenePhase) { newPhase in
24 | switch newPhase {
25 | case .background: LifecycleRegistryExtKt.stop(rootHolder.lifecycle)
26 | case .inactive: LifecycleRegistryExtKt.pause(rootHolder.lifecycle)
27 | case .active: LifecycleRegistryExtKt.resume(rootHolder.lifecycle)
28 | @unknown default: break
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/apikey/config/ApiKeyConfig.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.authorization.apikey.config
2 |
3 | import org.springframework.boot.autoconfigure.domain.EntityScan
4 | import org.springframework.context.annotation.ComponentScan
5 | import org.springframework.context.annotation.Configuration
6 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories
7 | import org.springframework.transaction.annotation.EnableTransactionManagement
8 |
9 | /**
10 | * Configuration for the API key authorization module.
11 | * This class enables component scanning and JPA repositories for the module.
12 | */
13 | @Configuration
14 | @EnableTransactionManagement
15 | @EnableJpaRepositories("band.effective.office.backend.feature.authorization.apikey.repository")
16 | @EntityScan(basePackages = ["band.effective.office.backend.feature.authorization.apikey.entity"])
17 | @ComponentScan("band.effective.office.backend.feature.authorization.apikey")
18 | class ApiKeyConfig
19 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/booking/RecurrenceDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto.booking
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | /**
6 | * Represents a recurrence pattern for bookings.
7 | * @property interval Period of bookings (e.g., every 2 days)
8 | * @property freq Frequency type (DAILY, WEEKLY, MONTHLY, YEARLY)
9 | * @property count Number of occurrences
10 | * @property until End date of recurrence
11 | * @property byDay Days of the week for recurrence
12 | * @property byMonth Months for recurrence
13 | * @property byYearDay Days of the year for recurrence
14 | * @property byHour Hours for recurrence
15 | */
16 | @Serializable
17 | data class RecurrenceDTO(
18 | val interval: Int? = null,
19 | val freq: String,
20 | val count: Int? = null,
21 | val until: Long? = null,
22 | val byDay: List = listOf(),
23 | val byMonth: List = listOf(),
24 | val byYearDay: List = listOf(),
25 | val byHour: List = listOf()
26 | )
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/entity/WorkspaceEntity.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.Entity
5 | import jakarta.persistence.Id
6 | import jakarta.persistence.JoinColumn
7 | import jakarta.persistence.OneToMany
8 | import jakarta.persistence.ManyToOne
9 | import jakarta.persistence.Table
10 | import java.util.UUID
11 |
12 | @Entity
13 | @Table(name = "workspaces")
14 | data class WorkspaceEntity(
15 | @Id
16 | val id: UUID = UUID.randomUUID(),
17 | @Column(name = "name", nullable = false, unique = true, length = 255)
18 | val name: String,
19 | @Column(name = "tag", nullable = false, unique = false, length = 255)
20 | val tag: String,
21 | @OneToMany(mappedBy = "workspace")
22 | val workspaceUtilities: List = emptyList(),
23 |
24 | @ManyToOne
25 | @JoinColumn(name = "zone_id")
26 | var zone: WorkspaceZoneEntity? = null
27 | )
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/controller/NotificationDeduplicator.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.controller
2 |
3 | import java.util.concurrent.ConcurrentHashMap
4 | import kotlin.time.Clock
5 | import kotlin.time.Duration.Companion.seconds
6 | import kotlin.time.ExperimentalTime
7 | import kotlin.time.Instant
8 | import org.springframework.stereotype.Component
9 |
10 | @Component
11 | class NotificationDeduplicator {
12 |
13 | private val ttlSeconds = 5L
14 |
15 | @OptIn(ExperimentalTime::class)
16 | private val seenEvents = ConcurrentHashMap()
17 |
18 | @OptIn(ExperimentalTime::class)
19 | fun isDuplicate(eventId: String): Boolean {
20 | val now = Clock.System.now()
21 |
22 | // Очистка устаревших
23 | seenEvents.entries.removeIf { (_, timestamp) ->
24 | timestamp + ttlSeconds.seconds < now
25 | }
26 |
27 | // Добавим, если ещё нет
28 | return seenEvents.putIfAbsent(eventId, now) != null
29 | }
30 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/common/IconSuccess.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.common
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.size
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.unit.dp
10 | import band.effective.office.tablet.core.ui.Res
11 | import band.effective.office.tablet.core.ui.check
12 | import band.effective.office.tablet.core.ui.theme.LocalCustomColorsPalette
13 | import org.jetbrains.compose.resources.vectorResource
14 |
15 | @Composable
16 | fun IconSuccess() {
17 | Box(
18 | contentAlignment = Alignment.Center
19 | ) {
20 | Icon(
21 | imageVector = vectorResource(Res.drawable.check),
22 | contentDescription = "Check",
23 | modifier = Modifier.size(60.dp),
24 | tint = LocalCustomColorsPalette.current.onSuccess
25 | )
26 | }
27 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/GetEventsFlowUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import band.effective.office.tablet.core.domain.Either
4 | import band.effective.office.tablet.core.domain.ErrorWithData
5 | import band.effective.office.tablet.core.domain.model.RoomInfo
6 | import band.effective.office.tablet.core.domain.repository.LocalRoomRepository
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | /**
10 | * Use case for getting a flow of room information updates.
11 | *
12 | * @property localRoomRepository Repository for local room storage operations
13 | */
14 | class GetEventsFlowUseCase(
15 | private val localRoomRepository: LocalRoomRepository,
16 | ) {
17 | /**
18 | * Returns a flow of room information updates from the local repository.
19 | * @return Flow of Either containing room information or error with saved data
20 | */
21 | operator fun invoke(): Flow>, List>> {
22 | return localRoomRepository.subscribeOnUpdates()
23 | }
24 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/utils/KioskManager.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.utils
2 |
3 | import android.app.Activity
4 | import android.app.admin.DevicePolicyManager
5 | import android.content.Context
6 | import band.effective.office.tablet.AdminReceiver
7 |
8 | class KioskManager(private val context: Context) {
9 |
10 | private val devicePolicyManager: DevicePolicyManager =
11 | context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
12 |
13 | private val adminComponent = AdminReceiver.Companion.getComponentName(context)
14 | private val packageName = context.packageName
15 |
16 |
17 | fun enableKioskMode(activity: Activity) {
18 | if (!devicePolicyManager.isDeviceOwnerApp(packageName)) return
19 | devicePolicyManager.setLockTaskPackages(adminComponent, arrayOf(packageName))
20 | activity.startLockTask()
21 | }
22 |
23 | fun disableKioskMode(activity: Activity) {
24 | if (!devicePolicyManager.isDeviceOwnerApp(packageName)) return
25 | activity.stopLockTask()
26 | }
27 | }
--------------------------------------------------------------------------------
/backend/feature/calendar-subscription/src/main/kotlin/band/effective/office/backend/feature/calendar/subscription/CalendarSubscriptionModule.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.calendar.subscription
2 |
3 | import band.effective.office.backend.feature.calendar.subscription.scheduler.CalendarSubscriptionScheduler
4 | import jakarta.annotation.PostConstruct
5 | import org.springframework.boot.context.properties.EnableConfigurationProperties
6 | import org.springframework.context.annotation.ComponentScan
7 | import org.springframework.context.annotation.Configuration
8 |
9 | /**
10 | * Configuration class for the calendar subscription module.
11 | * This class enables component scanning and configuration properties for the module.
12 | */
13 | @Configuration
14 | @ComponentScan
15 | @EnableConfigurationProperties
16 | class CalendarSubscriptionModule(
17 | private val scheduler: CalendarSubscriptionScheduler,
18 | ) {
19 | /**
20 | * Initializes the calendar subscription module on application startup.
21 | */
22 | @PostConstruct
23 | fun init() {
24 | scheduler.init()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/ethernet.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/utils/Converter.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.utils
2 |
3 | import band.effective.office.tablet.core.data.dto.user.UserDTO
4 | import band.effective.office.tablet.core.data.dto.workspace.WorkspaceDTO
5 | import band.effective.office.tablet.core.domain.model.Organizer
6 | import band.effective.office.tablet.core.domain.model.RoomInfo
7 |
8 | object Converter {
9 | fun RoomInfo.toDto(): WorkspaceDTO =
10 | WorkspaceDTO(
11 | id = id,
12 | name = name,
13 | utilities = listOf(),
14 | zone = null,
15 | tag = "meeting"
16 | )
17 |
18 | fun Organizer.toDto(): UserDTO =
19 | UserDTO(
20 | id = id,
21 | fullName = fullName,
22 | active = false,
23 | role = "",
24 | avatarUrl = "",
25 | integrations = null,
26 | email = email!!,
27 | tag = "employee"
28 | )
29 |
30 | fun UserDTO.toOrganizer() = Organizer(fullName = fullName, id = id, email = email)
31 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 effective.dev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/api/Collector.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.api
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 | import kotlinx.coroutines.flow.SharingStarted
6 | import kotlinx.coroutines.flow.map
7 | import kotlinx.coroutines.flow.shareIn
8 | import kotlinx.coroutines.flow.update
9 |
10 | /**
11 | * Utility class for collecting and sharing data updates.
12 | * @param T Type of data to collect
13 | * @param defaultValue Default value for the data
14 | */
15 | // TODO rename
16 | class Collector(defaultValue: T) {
17 | private data class CollectableElement(val value: T, val number: Long)
18 |
19 | private val collection = MutableStateFlow(CollectableElement(defaultValue, 0))
20 |
21 | fun flow() =
22 | collection.map { it.value }
23 |
24 | /**
25 | * Emits a new value to the collection.
26 | * @param value New value to emit
27 | */
28 | fun emit(value: T) {
29 | collection.update { CollectableElement(value = value, number = it.number + 1) }
30 | }
31 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/util/LocalDateTimeFormatter.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.util
2 |
3 | import kotlinx.datetime.LocalDateTime
4 | import kotlinx.datetime.LocalTime
5 | import kotlinx.datetime.format.FormatStringsInDatetimeFormats
6 | import kotlinx.datetime.format.byUnicodePattern
7 |
8 | private const val formatPattern = "HH:mm"
9 |
10 | @OptIn(FormatStringsInDatetimeFormats::class)
11 | /**
12 | * Format time as 09:07
13 | */
14 | val timeFormatter = LocalDateTime.Format {
15 | byUnicodePattern(formatPattern)
16 | }
17 |
18 | @OptIn(FormatStringsInDatetimeFormats::class)
19 | fun LocalDateTime.toFormattedString(pattern: String): String {
20 | val formatter = LocalDateTime.Format {
21 | byUnicodePattern(pattern)
22 | }
23 | return formatter.format(this)
24 | }
25 |
26 | @OptIn(FormatStringsInDatetimeFormats::class)
27 | fun LocalTime.toFormattedString(pattern: String): String {
28 | val formatter = LocalTime.Format {
29 | byUnicodePattern(pattern)
30 | }
31 | return formatter.format(this)
32 | }
33 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/repository/RoomRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.repository
2 |
3 | import band.effective.office.tablet.core.domain.Either
4 | import band.effective.office.tablet.core.domain.ErrorWithData
5 | import band.effective.office.tablet.core.domain.model.RoomInfo
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | /**
9 | * Repository interface for room-related operations.
10 | * Provides methods for retrieving information about rooms and their bookings.
11 | */
12 | interface RoomRepository {
13 | /**
14 | * Subscribes to updates about rooms and their bookings.
15 | *
16 | * @return Flow of Either containing room information or an error with saved data
17 | */
18 | fun subscribeOnUpdates(): Flow>, List>>
19 |
20 | /**
21 | * Gets information about all rooms with their bookings.
22 | *
23 | * @return Either containing room information or an error with saved data
24 | */
25 | suspend fun getRoomsInfo(): Either>, List>
26 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/not_wifi.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/common/SuccessText.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.common
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import band.effective.office.tablet.core.ui.Res
10 | import band.effective.office.tablet.core.ui.success_text
11 | import band.effective.office.tablet.core.ui.theme.LocalCustomColorsPalette
12 | import band.effective.office.tablet.core.ui.theme.h2
13 | import org.jetbrains.compose.resources.stringResource
14 |
15 | @Composable
16 | fun SuccessText(modifier: Modifier, nameRoom: String) {
17 | Box(
18 | modifier = modifier,
19 | contentAlignment = Alignment.Center
20 | ) {
21 | Text(
22 | text = stringResource(Res.string.success_text, nameRoom),
23 | style = MaterialTheme.typography.h2,
24 | color = LocalCustomColorsPalette.current.primaryTextAndIcon
25 | )
26 | }
27 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/CurrentTimeHolder.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.domain
2 |
3 | import kotlinx.coroutines.flow.MutableStateFlow
4 | import kotlinx.coroutines.flow.StateFlow
5 | import kotlinx.coroutines.flow.asStateFlow
6 | import kotlinx.datetime.Clock
7 | import kotlinx.datetime.LocalDateTime
8 | import kotlinx.datetime.TimeZone
9 | import kotlinx.datetime.toLocalDateTime
10 |
11 | /**
12 | * A singleton that holds the current time.
13 | */
14 | object CurrentTimeHolder {
15 | private val defaultTimeZone = TimeZone.Companion.currentSystemDefault()
16 |
17 | private val _currentTime = MutableStateFlow(Clock.System.now().toLocalDateTime(defaultTimeZone))
18 | val currentTime: StateFlow = _currentTime.asStateFlow()
19 |
20 | /**
21 | * Updates the current time.
22 | */
23 | fun updateTime(time: LocalDateTime) {
24 | _currentTime.value = time
25 | }
26 |
27 | /**
28 | * Gets the current time.
29 | */
30 | fun getCurrentTime(): LocalDateTime {
31 | return _currentTime.value
32 | }
33 | }
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/service/DeviceService.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.service
2 |
3 | import band.effective.office.backend.feature.notifications.repository.DeviceRepository
4 | import band.effective.office.backend.feature.notifications.repository.entity.DeviceEntity
5 | import org.springframework.stereotype.Service
6 | import org.springframework.transaction.annotation.Transactional
7 |
8 | /**
9 | * Service for managing devices
10 | */
11 | @Service
12 | class DeviceService(private val deviceRepository: DeviceRepository) {
13 |
14 | /**
15 | * Retrieves all registered devices
16 | */
17 | @Transactional(readOnly = true)
18 | fun getAllDevices(): List {
19 | return deviceRepository.findAll()
20 | }
21 |
22 | /**
23 | * Checks if device exists by device ID
24 | *
25 | * @return true if device exists, false otherwise
26 | */
27 | @Transactional(readOnly = true)
28 | fun deviceExists(deviceId: String): Boolean {
29 | return deviceRepository.existsByDeviceId(deviceId)
30 | }
31 | }
--------------------------------------------------------------------------------
/backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/core/exception/AuthorizationExceptions.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.authorization.core.exception
2 |
3 | /**
4 | * Base class for all authorization-related exceptions.
5 | */
6 | sealed class AuthorizationException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
7 |
8 | /**
9 | * Exception thrown when authentication fails.
10 | */
11 | class AuthenticationException(message: String, cause: Throwable? = null) : AuthorizationException(message, cause)
12 |
13 | /**
14 | * Exception thrown when a token is invalid.
15 | */
16 | class InvalidTokenException(message: String, cause: Throwable? = null) : AuthorizationException(message, cause)
17 |
18 | /**
19 | * Exception thrown when a token has expired.
20 | */
21 | class TokenExpiredException(message: String, cause: Throwable? = null) : AuthorizationException(message, cause)
22 |
23 | /**
24 | * Exception thrown when a user does not have the required permissions.
25 | */
26 | class AccessDeniedException(message: String, cause: Throwable? = null) : AuthorizationException(message, cause)
--------------------------------------------------------------------------------
/backend/feature/calendar-subscription/src/main/kotlin/band/effective/office/backend/feature/calendar/subscription/repository/ChannelRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.calendar.subscription.repository
2 |
3 | import band.effective.office.backend.feature.calendar.subscription.repository.entity.ChannelEntity
4 | import org.springframework.data.jpa.repository.JpaRepository
5 | import org.springframework.stereotype.Repository
6 |
7 | /**
8 | * Repository for accessing Google Calendar notification channel entities in the database.
9 | */
10 | @Repository
11 | interface ChannelRepository : JpaRepository {
12 | /**
13 | * Find a channel by calendar ID.
14 | *
15 | * @param calendarId the calendar ID to search for
16 | * @return the channel entity if found, null otherwise
17 | */
18 | fun findByCalendarId(calendarId: String): ChannelEntity?
19 |
20 | /**
21 | * Find a channel by channel ID.
22 | *
23 | * @param channelId the channel ID to search for
24 | * @return the channel entity if found, null otherwise
25 | */
26 | fun findByChannelId(channelId: String): ChannelEntity?
27 | }
28 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/date/DatetimeUiUtils.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.date
2 |
3 | import kotlinx.datetime.LocalDateTime
4 | import kotlinx.datetime.format
5 | import kotlinx.datetime.format.MonthNames
6 | import kotlinx.datetime.format.Padding
7 | import kotlinx.datetime.format.char
8 |
9 | val dateTimeFormat = LocalDateTime.Format {
10 | hour(padding = Padding.ZERO)
11 | char(':')
12 | minute(padding = Padding.ZERO)
13 | char(' ')
14 | dayOfMonth(padding = Padding.ZERO)
15 | char(' ')
16 | monthName(MonthNames.ENGLISH_FULL)
17 | }
18 |
19 | val dayMonthFormat = LocalDateTime.Format {
20 | dayOfMonth(padding = Padding.ZERO)
21 | char(' ')
22 | monthName(MonthNames.ENGLISH_FULL)
23 | }
24 |
25 | val timeFormatter = LocalDateTime.Format {
26 | hour(padding = Padding.ZERO)
27 | char(':')
28 | minute(padding = Padding.ZERO)
29 | }
30 |
31 | fun LocalDateTime.time(): String = format(timeFormatter)
32 |
33 | val dateFormatter = LocalDateTime.Format {
34 | dayOfMonth(padding = Padding.ZERO)
35 | char(' ')
36 | monthName(MonthNames.ENGLISH_FULL)
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/GetRoomByNameUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import band.effective.office.tablet.core.domain.model.RoomInfo
4 | import band.effective.office.tablet.core.domain.repository.LocalRoomRepository
5 | import band.effective.office.tablet.core.domain.unbox
6 |
7 | /**
8 | * Use case for getting information about a specific room by its name.
9 | *
10 | * @property localRoomRepository Repository for local room storage operations
11 | */
12 | class GetRoomByNameUseCase(
13 | private val localRoomRepository: LocalRoomRepository,
14 | ) {
15 | /**
16 | * Gets information about a specific room by its name.
17 | *
18 | * @param roomName Name of the room to get information about
19 | * @return Room information or null if the room is not found
20 | */
21 | suspend operator fun invoke(roomName: String): RoomInfo? {
22 | val rooms = localRoomRepository.getRoomsInfo().unbox(
23 | errorHandler = { it.saveData }
24 | )
25 | val room = rooms?.firstOrNull { it.name == roomName }
26 | return room
27 | }
28 | }
--------------------------------------------------------------------------------
/backend/feature/user/src/main/kotlin/band/effective/office/backend/feature/user/repository/UserRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.user.repository
2 |
3 | import band.effective.office.backend.feature.user.repository.entity.UserEntity
4 | import org.springframework.data.jpa.repository.JpaRepository
5 | import org.springframework.stereotype.Repository
6 | import java.util.UUID
7 |
8 | /**
9 | * Repository for accessing User entities in the database.
10 | */
11 | @Repository
12 | interface UserRepository : JpaRepository {
13 | /**
14 | * Find a user by username.
15 | *
16 | * @param username the username to search for
17 | * @return the user entity if found, null otherwise
18 | */
19 | fun findByUsername(username: String): UserEntity?
20 |
21 | /**
22 | * Find a user by email.
23 | *
24 | * @param email the email to search for
25 | * @return the user entity if found, null otherwise
26 | */
27 | fun findByEmail(email: String): UserEntity?
28 |
29 | /**
30 | * Find all active users.
31 | *
32 | * @return a list of active user entities
33 | */
34 | fun findByActiveTrue(): List
35 | }
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/dto/WorkspaceDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.dto
2 |
3 | import band.effective.office.backend.feature.booking.core.dto.BookingDto
4 | import io.swagger.v3.oas.annotations.media.Schema
5 |
6 | /**
7 | * Data Transfer Object for a workspace in the office.
8 | */
9 | @Schema(description = "Workspace in the office")
10 | data class WorkspaceDTO(
11 | @Schema(description = "Unique identifier of the workspace", example = "550e8400-e29b-41d4-a716-446655440000")
12 | val id: String,
13 |
14 | @Schema(description = "Name of the workspace", example = "Meeting Room 1")
15 | val name: String,
16 |
17 | @Schema(description = "List of utilities available in the workspace")
18 | val utilities: List,
19 |
20 | @Schema(description = "Zone where the workspace is located")
21 | val zone: WorkspaceZoneDTO? = null,
22 |
23 | @Schema(description = "Tag for categorizing the workspace", example = "meeting")
24 | val tag: String,
25 |
26 | @Schema(description = "List of bookings for this workspace")
27 | val bookings: List? = null
28 | )
29 |
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/CalendarIdRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository
2 |
3 | import band.effective.office.backend.feature.workspace.core.repository.entity.CalendarIdEntity
4 | import java.util.UUID
5 | import org.springframework.data.jpa.repository.JpaRepository
6 | import org.springframework.stereotype.Repository
7 |
8 | /**
9 | * Repository for accessing calendar ID entities in the database.
10 | */
11 | @Repository
12 | interface CalendarIdRepository : JpaRepository {
13 | /**
14 | * Find a calendar ID entity by workspace ID.
15 | *
16 | * @param workspaceId the workspace ID to search for
17 | * @return the calendar ID entity if found, null otherwise
18 | */
19 | fun findByWorkspaceId(workspaceId: UUID): CalendarIdEntity?
20 |
21 | /**
22 | * Find a calendar ID entity by calendar ID string.
23 | *
24 | * @param calendarId the calendar ID string to search for
25 | * @return the calendar ID entity if found, null otherwise
26 | */
27 | fun findByCalendarId(calendarId: String): CalendarIdEntity?
28 | }
29 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/button/SuccessButton.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.button
2 |
3 | import androidx.compose.foundation.layout.RowScope
4 | import androidx.compose.foundation.shape.RoundedCornerShape
5 | import androidx.compose.material3.Button
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.draw.clip
9 | import androidx.compose.ui.unit.dp
10 |
11 | /**
12 | * A button with a rounded corner shape for success actions
13 | *
14 | * @param modifier Modifier to be applied to the button
15 | * @param onClick Callback to be invoked when the button is clicked
16 | * @param enable Whether the button is enabled
17 | * @param content Content to be displayed inside the button
18 | */
19 | @Composable
20 | fun SuccessButton(
21 | modifier: Modifier = Modifier,
22 | onClick: () -> Unit,
23 | enable: Boolean = true,
24 | content: @Composable RowScope.() -> Unit
25 | ) {
26 | Button(
27 | modifier = modifier.clip(RoundedCornerShape(100.dp)),
28 | enabled = enable,
29 | onClick = { onClick() }
30 | ) {
31 | content()
32 | }
33 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/res/xml/device_admin_receiver.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/entity/CalendarIdEntity.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.Entity
5 | import jakarta.persistence.Id
6 | import jakarta.persistence.JoinColumn
7 | import jakarta.persistence.ManyToOne
8 | import jakarta.persistence.Table
9 | import java.time.LocalDateTime
10 | import java.util.UUID
11 |
12 | /**
13 | * JPA entity representing a calendar ID linked to a workspace.
14 | */
15 | @Entity
16 | @Table(name = "calendar_ids")
17 | data class CalendarIdEntity(
18 | @Id
19 | val id: UUID = UUID.randomUUID(),
20 |
21 | @ManyToOne
22 | @JoinColumn(name = "workspace_id", nullable = false, unique = true)
23 | val workspace: WorkspaceEntity,
24 |
25 | @Column(name = "calendar_id", nullable = false, unique = true, length = 255)
26 | val calendarId: String,
27 |
28 | @Column(name = "created_at", nullable = false)
29 | val createdAt: LocalDateTime = LocalDateTime.now(),
30 |
31 | @Column(name = "updated_at", nullable = false)
32 | val updatedAt: LocalDateTime = LocalDateTime.now()
33 | )
--------------------------------------------------------------------------------
/backend/feature/calendar-subscription/src/main/kotlin/band/effective/office/backend/feature/calendar/subscription/repository/entity/ChannelEntity.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.calendar.subscription.repository.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.Entity
5 | import jakarta.persistence.Id
6 | import jakarta.persistence.Table
7 | import java.time.LocalDateTime
8 |
9 | /**
10 | * JPA entity representing a Google Calendar notification channel in the database.
11 | */
12 | @Entity
13 | @Table(name = "calendar_channels")
14 | class ChannelEntity(
15 | @Id
16 | @Column(name = "calendar_id", nullable = false)
17 | val calendarId: String,
18 |
19 | @Column(name = "channel_id", nullable = false)
20 | val channelId: String,
21 |
22 | @Column(name = "resource_id", nullable = false)
23 | val resourceId: String,
24 |
25 | @Column(nullable = true)
26 | val type: String,
27 |
28 | @Column(nullable = false)
29 | val address: String,
30 |
31 | @Column(name = "created_at", nullable = false)
32 | val createdAt: LocalDateTime = LocalDateTime.now(),
33 |
34 | @Column(name = "updated_at", nullable = false)
35 | val updatedAt: LocalDateTime = LocalDateTime.now()
36 | )
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/GetCurrentRoomInfosUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import band.effective.office.tablet.core.domain.Either
4 | import band.effective.office.tablet.core.domain.ErrorWithData
5 | import band.effective.office.tablet.core.domain.model.RoomInfo
6 | import band.effective.office.tablet.core.domain.repository.LocalRoomRepository
7 |
8 | /**
9 | * Use case for getting the current information about all rooms from the local repository.
10 | *
11 | * @property localRoomRepository Repository for local room storage operations
12 | */
13 | class GetCurrentRoomInfosUseCase(
14 | private val localRoomRepository: LocalRoomRepository,
15 | ) {
16 | /**
17 | * Gets the current information about all rooms from the local repository without refreshing from the network.
18 | * This is useful when you need the most recent locally cached data without network latency.
19 | *
20 | * @return Either containing room information or an error with saved data
21 | */
22 | suspend operator fun invoke(): Either>, List> {
23 | return localRoomRepository.getRoomsInfo()
24 | }
25 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/disconnect.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
12 |
17 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/clients/tablet/feature/slot/src/commonMain/kotlin/band/effective/office/tablet/feature/slot/domain/usecase/GetSlotsByRoomUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.slot.domain.usecase
2 |
3 | import band.effective.office.tablet.core.domain.OfficeTime
4 | import band.effective.office.tablet.core.domain.model.RoomInfo
5 | import band.effective.office.tablet.core.domain.model.Slot
6 | import band.effective.office.tablet.core.domain.useCase.SlotUseCase
7 | import band.effective.office.tablet.core.domain.util.currentLocalDateTime
8 | import band.effective.office.tablet.core.domain.util.roundUpToNextQuarter
9 | import kotlinx.datetime.LocalDateTime
10 |
11 | class GetSlotsByRoomUseCase(
12 | private val slotUseCase: SlotUseCase,
13 | ) {
14 |
15 | operator fun invoke(
16 | roomInfo: RoomInfo,
17 | start: LocalDateTime = currentLocalDateTime,
18 | finish: LocalDateTime = OfficeTime.finishWorkTime(start.date)
19 | ): List {
20 | val roundedStart = roundUpToNextQuarter(start)
21 | return slotUseCase.getSlots(
22 | currentEvent = roomInfo.currentEvent,
23 | events = roomInfo.eventList,
24 | start = roundedStart,
25 | finish = finish
26 | )
27 | }
28 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/kotlin/band/effective/office/tablet/components/VersionOverlay.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.components
2 |
3 | import androidx.compose.foundation.layout.padding
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.text.style.TextAlign
10 | import androidx.compose.ui.unit.dp
11 | import androidx.compose.ui.unit.sp
12 | import band.effective.office.tablet.BuildKonfig
13 | import androidx.compose.foundation.layout.BoxScope
14 |
15 | @Composable
16 | fun BoxScope.VersionOverlay(
17 | modifier: Modifier = Modifier,
18 | text: String = "v${BuildKonfig.VERSION_NAME}",
19 | ) {
20 | Text(
21 | text = text,
22 | style = MaterialTheme.typography.labelSmall.copy(fontSize = 12.sp),
23 | color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.35f),
24 | textAlign = TextAlign.Start,
25 | modifier = modifier
26 | .align(Alignment.BottomEnd)
27 | .padding(
28 | end = 40.dp,
29 | bottom = 10.dp
30 | )
31 | )
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/EventInfo.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.model
2 |
3 | import band.effective.office.tablet.core.domain.util.currentLocalDateTime
4 | import kotlinx.datetime.LocalDateTime
5 | import kotlinx.serialization.Serializable
6 |
7 | /**
8 | * Domain model representing an event or booking.
9 | */
10 | @Serializable
11 | data class EventInfo(
12 | val startTime: LocalDateTime,
13 | val finishTime: LocalDateTime,
14 | val organizer: Organizer,
15 | val id: String,
16 | val isLoading: Boolean,
17 | val isEditable: Boolean = true,
18 | ) {
19 | init {
20 | // Validate that start time is before finish time
21 | require(startTime <= finishTime) { "Start time must be before or equal to finish time" }
22 | }
23 |
24 | companion object {
25 | const val defaultId: String = ""
26 |
27 | val emptyEvent = EventInfo(
28 | startTime = currentLocalDateTime,
29 | finishTime = currentLocalDateTime,
30 | organizer = Organizer.Companion.default,
31 | id = defaultId,
32 | isLoading = true,
33 | )
34 | }
35 |
36 | fun isNotCreated() = id == defaultId
37 | }
38 |
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/mapper/WorkspaceMapper.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository.mapper
2 |
3 | import band.effective.office.backend.core.domain.model.Utility
4 | import band.effective.office.backend.core.domain.model.Workspace
5 | import band.effective.office.backend.core.domain.model.WorkspaceZone
6 | import band.effective.office.backend.feature.workspace.core.repository.entity.WorkspaceEntity
7 | import band.effective.office.backend.feature.workspace.core.repository.entity.WorkspaceUtilityEntity
8 | import org.springframework.stereotype.Component
9 |
10 | @Component
11 | object WorkspaceMapper {
12 |
13 | fun toDomain(entity: WorkspaceEntity): Workspace = Workspace(
14 | id = entity.id,
15 | name = entity.name,
16 | tag = entity.tag,
17 | utilities = entity.workspaceUtilities.map { toDomain(it) },
18 | zone = entity.zone?.let { WorkspaceZone(it.id, it.name) },
19 | )
20 |
21 | private fun toDomain(entity: WorkspaceUtilityEntity): Utility = Utility(
22 | id = entity.utility.id,
23 | name = entity.utility.name,
24 | iconUrl = entity.utility.iconUrl,
25 | count = entity.count
26 | )
27 | }
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/presentation/main/State.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.presentation.main
2 |
3 | import band.effective.office.tablet.core.domain.model.RoomInfo
4 | import band.effective.office.tablet.core.domain.util.currentLocalDateTime
5 | import kotlinx.datetime.LocalDateTime
6 |
7 | data class State(
8 | val isLoad: Boolean,
9 | val isData: Boolean,
10 | val isError: Boolean,
11 | val isDisconnect: Boolean,
12 | val updatedEvent: Any,
13 | val roomList: List,
14 | val indexSelectRoom: Int,
15 | val timeToNextEvent: Int,
16 | val selectedDate: LocalDateTime,
17 | val currentDate: LocalDateTime,
18 | ) {
19 | companion object {
20 | val defaultState =
21 | State(
22 | isLoad = true,
23 | isData = false,
24 | isError = false,
25 | isDisconnect = false,
26 | updatedEvent = Any(),
27 | roomList = listOf(),
28 | indexSelectRoom = 0,
29 | timeToNextEvent = 0,
30 | selectedDate = currentLocalDateTime,
31 | currentDate = currentLocalDateTime,
32 | )
33 | }
34 | }
--------------------------------------------------------------------------------
/backend/feature/booking/core/src/main/kotlin/band/effective/office/backend/feature/booking/core/domain/model/Booking.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.booking.core.domain.model
2 |
3 | import band.effective.office.backend.core.domain.model.User
4 | import band.effective.office.backend.core.domain.model.Workspace
5 | import java.time.Instant
6 | import java.util.UUID
7 |
8 | /**
9 | * Represents a booking of a workspace.
10 | *
11 | * A booking is created by a user (owner) for a specific workspace and time period.
12 | * It can optionally have participants, a recurrence pattern, and an ID
13 | * from a calendar provider.
14 | *
15 | * For recurring bookings, the recurringBookingId field links individual booking instances
16 | * to their parent recurring booking.
17 | */
18 | data class Booking(
19 | val id: String = "",
20 | val owner: User?,
21 | val participants: List = emptyList(),
22 | val workspace: Workspace,
23 | val beginBooking: Instant,
24 | val endBooking: Instant,
25 | val recurrence: RecurrenceModel? = null,
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
28 | )
29 |
--------------------------------------------------------------------------------
/backend/core/domain/src/main/kotlin/band/effective/office/backend/core/domain/model/User.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.core.domain.model
2 |
3 | import jakarta.validation.constraints.Email
4 | import jakarta.validation.constraints.NotBlank
5 | import jakarta.validation.constraints.Size
6 | import java.time.LocalDateTime
7 | import java.util.UUID
8 |
9 | /**
10 | * Represents a user in the system.
11 | */
12 | data class User(
13 | val id: UUID = UUID.randomUUID(),
14 |
15 | @field:NotBlank(message = "Username is required")
16 | @field:Size(min = 3, max = 255, message = "Username must be between 3 and 50 characters")
17 | val username: String,
18 |
19 | @field:NotBlank(message = "Email is required")
20 | @field:Email(message = "Email should be valid")
21 | val email: String,
22 |
23 | @field:NotBlank(message = "First name is required")
24 | val firstName: String,
25 |
26 | @field:NotBlank(message = "Last name is required")
27 | val lastName: String,
28 |
29 | val createdAt: LocalDateTime = LocalDateTime.now(),
30 |
31 | val updatedAt: LocalDateTime = LocalDateTime.now(),
32 |
33 | val active: Boolean = true,
34 |
35 | val role: String = "",
36 |
37 | val avatarUrl: String = "",
38 |
39 | val tag: String = ""
40 | )
41 |
--------------------------------------------------------------------------------
/backend/feature/workspace/src/main/kotlin/band/effective/office/backend/feature/workspace/core/repository/WorkspaceRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.workspace.core.repository
2 |
3 | import band.effective.office.backend.feature.workspace.core.repository.entity.WorkspaceEntity
4 | import band.effective.office.backend.feature.workspace.core.repository.entity.WorkspaceZoneEntity
5 | import java.util.UUID
6 | import org.springframework.data.jpa.repository.JpaRepository
7 | import org.springframework.data.jpa.repository.Query
8 | import org.springframework.stereotype.Repository
9 |
10 | /**
11 | * Repository interface for workspace-related database operations.
12 | */
13 | @Repository
14 | interface WorkspaceRepository : JpaRepository {
15 | /**
16 | * Returns all workspaces with the given tag.
17 | *
18 | * @param tag tag name of requested workspaces
19 | * @return List of [Workspace] with the given [tag]
20 | */
21 | fun findAllByTag(tag: String): List
22 |
23 | /**
24 | * Returns all workspace zones.
25 | *
26 | * @return List of all [WorkspaceZone]
27 | */
28 | @Query("SELECT z FROM WorkspaceZoneEntity z")
29 | fun findAllWorkspaceZones(): List
30 | }
31 |
--------------------------------------------------------------------------------
/backend/core/domain/src/main/kotlin/band/effective/office/backend/core/domain/service/UserDomainService.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.core.domain.service
2 |
3 | import band.effective.office.backend.core.domain.model.User
4 | import java.util.UUID
5 |
6 | /**
7 | * Service interface for user operations.
8 | * This interface defines the contract for any user service implementation.
9 | */
10 | interface UserDomainService {
11 | /**
12 | * Find a user by username.
13 | *
14 | * @param username The username to search for.
15 | * @return The user if found, null otherwise.
16 | */
17 | fun findByUsername(username: String): User?
18 |
19 | /**
20 | * Find a user by email.
21 | *
22 | * @param email The email to search for.
23 | * @return The user if found, null otherwise.
24 | */
25 | fun findByEmail(email: String): User?
26 |
27 | /**
28 | * Find a user by ID.
29 | *
30 | * @param id The user ID to search for.
31 | * @return The user if found, null otherwise.
32 | */
33 | fun findById(id: UUID): User?
34 |
35 | /**
36 | * Create a new user.
37 | *
38 | * @param user The user to create.
39 | * @return The created user.
40 | */
41 | fun createUser(user: User): User
42 | }
43 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/commonMain/composeResources/drawable/ic_light_mode.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/EitherUtils.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain
2 |
3 | fun Either.unbox(
4 | errorHandler: (ErrorType) -> DataType,
5 | successHandler: ((DataType) -> DataType)? = null
6 | ): DataType =
7 | when (this) {
8 | is Either.Error -> errorHandler(this.error)
9 | is Either.Success -> successHandler?.invoke(data) ?: data
10 | }
11 |
12 | fun Either.map(
13 | errorMapper: (OldErrorType) -> ErrorType,
14 | successMapper: (oldDataType) -> DataType,
15 | ): Either =
16 | when (this) {
17 | is Either.Error -> Either.Error(errorMapper(this.error))
18 | is Either.Success -> Either.Success(successMapper(this.data))
19 | }
20 |
21 | suspend fun Either.asyncMap(
22 | errorMapper: suspend (OldErrorType) -> ErrorType,
23 | successMapper: suspend (oldDataType) -> DataType,
24 | ): Either =
25 | when (this) {
26 | is Either.Error -> Either.Error(errorMapper(this.error))
27 | is Either.Success -> Either.Success(successMapper(data))
28 | }
29 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/Settings.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.model
2 | import com.russhwolf.settings.*
3 |
4 | class SettingsManager(private val settings: Settings) {
5 |
6 | companion object {
7 | private const val KEY_NAME_ROOM = "nameRoom"
8 |
9 | // Singleton-like instance
10 | private var currentInstance: SettingsManager? = null
11 |
12 | fun init(settings: Settings) {
13 | if(currentInstance == null) {
14 | currentInstance = SettingsManager(settings)
15 | }
16 | }
17 |
18 | fun current(): SettingsManager {
19 | return currentInstance
20 | ?: error("SettingsManager not initialized. Call SettingsManager.init(settings) first.")
21 | }
22 | }
23 |
24 | var currentRoomName: String
25 | get() = settings.getString(KEY_NAME_ROOM, "")
26 | private set(value) {
27 | settings.putString(KEY_NAME_ROOM, value)
28 | }
29 |
30 | fun checkCurrentRoom(): String = currentRoomName
31 |
32 | fun updateSettings(newRoomName: String) {
33 | currentRoomName = newRoomName
34 | }
35 |
36 | fun removeRoomName() {
37 | settings.remove(KEY_NAME_ROOM)
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/utils/KioskLifecycleObserver.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.utils
2 |
3 | import android.app.Activity
4 | import androidx.lifecycle.DefaultLifecycleObserver
5 | import androidx.lifecycle.LifecycleOwner
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.Job
8 | import kotlinx.coroutines.flow.launchIn
9 | import kotlinx.coroutines.flow.onEach
10 |
11 | class KioskLifecycleObserver(
12 | private val activity: Activity,
13 | private val kioskManager: KioskManager,
14 | private val kioskCommandBus: KioskCommandBus,
15 | private val coroutineScope: CoroutineScope
16 | ) : DefaultLifecycleObserver {
17 |
18 | private var job: Job? = null
19 |
20 | override fun onStart(owner: LifecycleOwner) {
21 | super.onStart(owner)
22 | job = kioskCommandBus.commandFlow
23 | .onEach { command ->
24 | when (command) {
25 | KioskCommand.Enable -> kioskManager.enableKioskMode(activity)
26 | KioskCommand.Disable -> kioskManager.disableKioskMode(activity)
27 | }
28 | }
29 | .launchIn(coroutineScope)
30 | }
31 |
32 | override fun onStop(owner: LifecycleOwner) {
33 | job?.cancel()
34 | job = null
35 | }
36 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/Slot.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.model
2 |
3 | import band.effective.office.tablet.core.domain.util.asInstant
4 | import kotlinx.datetime.LocalDateTime
5 |
6 | sealed class Slot {
7 | abstract val start: LocalDateTime
8 | abstract val finish: LocalDateTime
9 |
10 | fun minuteDuration(): Int {
11 | val startMillis = start.asInstant.toEpochMilliseconds()
12 | val finishMillis = finish.asInstant.toEpochMilliseconds()
13 | return ((finishMillis - startMillis) / 60000).toInt()
14 | }
15 |
16 | data class EmptySlot(
17 | override val start: LocalDateTime,
18 | override val finish: LocalDateTime,
19 | ) : Slot()
20 |
21 | data class EventSlot(
22 | override val start: LocalDateTime,
23 | override val finish: LocalDateTime,
24 | val eventInfo: EventInfo
25 | ) : Slot()
26 |
27 | data class MultiEventSlot(
28 | override val start: LocalDateTime,
29 | override val finish: LocalDateTime,
30 | val events: List
31 | ) : Slot()
32 |
33 | data class LoadingEventSlot(
34 | override val start: LocalDateTime,
35 | override val finish: LocalDateTime,
36 | val eventInfo: EventInfo
37 | ) : Slot()
38 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/common/AlertButton.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.common
2 |
3 | import androidx.compose.foundation.border
4 | import androidx.compose.foundation.layout.RowScope
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material3.Button
7 | import androidx.compose.material3.ButtonDefaults
8 | import androidx.compose.material3.MaterialTheme
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.clip
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.unit.dp
14 |
15 | @Composable
16 | fun AlertButton(
17 | modifier: Modifier = Modifier,
18 | onClick: () -> Unit,
19 | enabled: Boolean = true,
20 | content: @Composable RowScope.() -> Unit
21 | ) {
22 | Button(
23 | modifier = modifier
24 | .clip(RoundedCornerShape(100.dp)).border(
25 | width = 3.dp,
26 | shape = RoundedCornerShape(100.dp),
27 | color = MaterialTheme.colorScheme.onPrimary
28 | ),
29 | colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
30 | onClick = { onClick() },
31 | enabled = enabled
32 | ) {
33 | content()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/booking/Alert.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.booking
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.unit.dp
13 | import band.effective.office.tablet.core.ui.theme.alertColor
14 |
15 | /**
16 | * Alert component for displaying warning or error messages
17 | *
18 | * @param modifier Modifier to be applied to the alert box
19 | * @param text Text to be displayed in the alert
20 | */
21 | @Composable
22 | fun Alert(modifier: Modifier = Modifier, text: String) {
23 | Box(
24 | modifier = modifier
25 | .background(
26 | color = alertColor,
27 | shape = RoundedCornerShape(15.dp)
28 | )
29 | .padding(horizontal = 10.dp, vertical = 5.dp),
30 | contentAlignment = Alignment.CenterStart
31 | ) {
32 | Text(
33 | text = text,
34 | style = MaterialTheme.typography.headlineSmall
35 | )
36 | }
37 | }
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/dto/booking/BookingResponseDTO.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.dto.booking
2 |
3 | import band.effective.office.tablet.core.data.dto.user.UserDTO
4 | import band.effective.office.tablet.core.data.dto.workspace.WorkspaceDTO
5 | import kotlinx.serialization.Serializable
6 |
7 | /**
8 | * Represents a response for a booking.
9 | * @property owner Owner of the booking
10 | * @property participants List of participants in the booking
11 | * @property workspace Workspace that is booked
12 | * @property id Unique identifier of the booking
13 | * @property beginBooking Start time of the booking (Unix timestamp)
14 | * @property endBooking End time of the booking (Unix timestamp)
15 | * @property recurrence Recurrence pattern for recurring bookings
16 | * @property recurringBookingId ID of the recurring booking series this booking belongs to
17 | * @property isEditable Flag indicating if the booking can be edited or deleted from tablet client
18 | */
19 | @Serializable
20 | data class BookingResponseDTO(
21 | val owner: UserDTO?,
22 | val participants: List,
23 | val workspace: WorkspaceDTO,
24 | val id: String,
25 | val beginBooking: Long,
26 | val endBooking: Long,
27 | val recurrence: RecurrenceDTO? = null,
28 | val recurringBookingId: String? = null,
29 | val isEditable: Boolean = true
30 | )
31 |
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/dto/DeviceDto.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.dto
2 |
3 | import band.effective.office.backend.feature.notifications.repository.entity.DeviceEntity
4 | import io.swagger.v3.oas.annotations.media.Schema
5 | import java.time.LocalDateTime
6 | import java.util.UUID
7 |
8 | /**
9 | * Data Transfer Object for device information.
10 | */
11 | @Schema(description = "Device information")
12 | data class DeviceDto(
13 | @Schema(description = "Unique device identifier", example = "123e4567-e89b-12d3-a456-426614174000")
14 | val id: UUID,
15 |
16 | @Schema(description = "Android device ID", example = "7ac6ddd9a731bbeb")
17 | val deviceId: String,
18 |
19 | @Schema(description = "Device tag (e.g., meeting room name)", example = "Meeting Room A")
20 | val tag: String,
21 |
22 | @Schema(description = "Device registration timestamp")
23 | val createdAt: LocalDateTime
24 | ) {
25 | companion object {
26 | /**
27 | * Creates a DeviceDto from a DeviceEntity.
28 | */
29 | fun fromEntity(entity: DeviceEntity): DeviceDto {
30 | return DeviceDto(
31 | id = entity.id,
32 | deviceId = entity.deviceId,
33 | tag = entity.tag,
34 | createdAt = entity.createdAt
35 | )
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/backend/feature/authorization/src/main/kotlin/band/effective/office/backend/feature/authorization/core/Authorizer.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.authorization.core
2 |
3 | import jakarta.servlet.http.HttpServletRequest
4 |
5 | /**
6 | * Interface for authorization chain of responsibility element.
7 | * Each authorizer in the chain is responsible for a specific type of authorization.
8 | */
9 | interface Authorizer {
10 | /**
11 | * Attempts to authorize the request.
12 | *
13 | * @param request The HTTP request to authorize
14 | * @return The authorization result containing success status and optional user information
15 | */
16 | fun authorize(request: HttpServletRequest): AuthorizationResult
17 |
18 | /**
19 | * Sets the next authorizer in the chain.
20 | *
21 | * @param next The next authorizer to process the request if this one fails
22 | * @return The next authorizer
23 | */
24 | fun setNext(next: Authorizer): Authorizer
25 | }
26 |
27 | /**
28 | * Result of an authorization attempt.
29 | *
30 | * @property success Whether the authorization was successful
31 | * @property errorMessage Optional error message if authorization failed
32 | * @property errorCode Optional error code if authorization failed
33 | */
34 | data class AuthorizationResult(
35 | val success: Boolean,
36 | val errorMessage: String? = null,
37 | val errorCode: Int? = null
38 | )
39 |
--------------------------------------------------------------------------------
/clients/tablet/feature/bookingEditor/src/commonMain/kotlin/band/effective/office/tablet/feature/bookingEditor/presentation/mapper/EventInfoMapper.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.bookingEditor.presentation.mapper
2 |
3 | import band.effective.office.tablet.core.domain.OfficeTime
4 | import band.effective.office.tablet.core.domain.model.EventInfo
5 | import band.effective.office.tablet.core.domain.model.Slot
6 | import band.effective.office.tablet.core.domain.util.asInstant
7 | import band.effective.office.tablet.feature.bookingEditor.presentation.State
8 |
9 | class EventInfoMapper {
10 |
11 | fun mapToSlot(eventInfo: EventInfo): Slot =
12 | Slot.EventSlot(start = eventInfo.startTime, finish = eventInfo.finishTime, eventInfo = eventInfo)
13 |
14 | fun mapToUpdateBookingState(eventInfo: EventInfo): State {
15 | val duration = eventInfo.finishTime.asInstant.minus(eventInfo.startTime.asInstant).inWholeMinutes.toInt()
16 | val finishWorkTime = OfficeTime.finishWorkTime(eventInfo.startTime.date)
17 | val canIncrementDuration = eventInfo.finishTime < finishWorkTime
18 | return State.defaultValue.copy(
19 | date = eventInfo.startTime,
20 | duration = duration,
21 | selectOrganizer = eventInfo.organizer,
22 | inputText = eventInfo.organizer.fullName,
23 | event = eventInfo,
24 | canIncrementDuration = canIncrementDuration
25 | )
26 | }
27 | }
--------------------------------------------------------------------------------
/backend/app/src/main/kotlin/band/effective/office/backend/app/exception/GlobalExceptionHandler.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.app.exception
2 |
3 | import band.effective.office.backend.core.data.ErrorDto
4 | import org.slf4j.LoggerFactory
5 | import org.springframework.http.HttpStatus
6 | import org.springframework.http.ResponseEntity
7 | import org.springframework.web.bind.annotation.ExceptionHandler
8 | import org.springframework.web.bind.annotation.RestControllerAdvice
9 |
10 | /**
11 | * Global exception handler for all unhandled exceptions.
12 | * This class catches any exception that isn't handled by more specific exception handlers
13 | * and returns a standardized error response with a 500 status code.
14 | */
15 | @RestControllerAdvice
16 | class GlobalExceptionHandler {
17 | private val logger = LoggerFactory.getLogger(this::class.java)
18 |
19 | /**
20 | * Exception handler for all unhandled exceptions.
21 | * Returns a 500 Internal Server Error with a standardized error response.
22 | */
23 | @ExceptionHandler(Exception::class)
24 | fun handleException(ex: Exception): ResponseEntity {
25 | logger.error("Unhandled exception: ", ex)
26 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
27 | ErrorDto(
28 | message = ex.message ?: "An unexpected error occurred",
29 | code = HttpStatus.INTERNAL_SERVER_ERROR.value()
30 | )
31 | )
32 | }
33 | }
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/kotlin/band/effective/office/tablet/core/ui/common/DateTimeView.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.ui.common
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.material3.MaterialTheme
5 | import androidx.compose.material3.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import band.effective.office.tablet.core.ui.Res
10 | import band.effective.office.tablet.core.ui.date_booking
11 | import band.effective.office.tablet.core.ui.theme.LocalCustomColorsPalette
12 | import band.effective.office.tablet.core.ui.theme.h5
13 | import band.effective.office.tablet.core.ui.utils.DateDisplayMapper
14 | import kotlinx.datetime.LocalDateTime
15 | import org.jetbrains.compose.resources.stringResource
16 |
17 | @Composable
18 | fun DateTimeView(
19 | modifier: Modifier,
20 | startTime: LocalDateTime,
21 | finishTime: LocalDateTime,
22 | ) {
23 | Box(
24 | modifier = modifier,
25 | contentAlignment = Alignment.Center
26 | ) {
27 | Text(
28 | text = stringResource(
29 | Res.string.date_booking,
30 | startTime,
31 | DateDisplayMapper.formatTime(startTime),
32 | DateDisplayMapper.formatTime(finishTime)
33 | ),
34 | style = MaterialTheme.typography.h5,
35 | color = LocalCustomColorsPalette.current.primaryTextAndIcon
36 | )
37 | }
38 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/repository/LocalRoomRepository.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.repository
2 |
3 | import band.effective.office.tablet.core.domain.Either
4 | import band.effective.office.tablet.core.domain.ErrorWithData
5 | import band.effective.office.tablet.core.domain.model.RoomInfo
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | /**
9 | * Repository interface for local room-related operations.
10 | * Extends [RoomRepository] to provide additional methods for local storage operations.
11 | */
12 | interface LocalRoomRepository {
13 | /**
14 | * Updates the local storage with room information.
15 | * This method is typically called after fetching data from a remote source.
16 | *
17 | * @param either Either containing room information or an error with saved data
18 | */
19 | fun updateRoomsInfo(either: Either>, List>)
20 |
21 | /**
22 | * Subscribes to updates about rooms and their bookings.
23 | *
24 | * @return Flow of Either containing room information or an error with saved data
25 | */
26 | fun subscribeOnUpdates(): Flow>, List>>
27 |
28 | /**
29 | * Gets information about all rooms with their bookings.
30 | *
31 | * @return Either containing room information or an error with saved data
32 | */
33 | suspend fun getRoomsInfo(): Either>, List>
34 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/util/BootstrapperTimer.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.util
2 |
3 | import band.effective.office.tablet.core.domain.useCase.TimerUseCase
4 | import kotlin.time.Duration
5 | import kotlin.time.Duration.Companion.seconds
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.IO
9 | import kotlinx.coroutines.Job
10 | import kotlinx.coroutines.launch
11 |
12 | class BootstrapperTimer(
13 | private val timerUseCase: TimerUseCase,
14 | private val coroutineScope: CoroutineScope,
15 | ) {
16 |
17 | private var job: Job? = null
18 | private var tickHandler: suspend (Long) -> Unit = {}
19 | private var timerDelay: Duration? = null
20 |
21 | fun init(
22 | delay: Duration,
23 | handler: suspend (Long) -> Unit,
24 | ) {
25 | tickHandler = handler
26 | timerDelay = delay
27 | }
28 |
29 | fun start(
30 | delay: Duration,
31 | handler: suspend (Long) -> Unit,
32 | ) {
33 | init(delay, handler)
34 | restart()
35 | }
36 |
37 | fun restart(delay: Duration = timerDelay ?: 1.seconds) {
38 | job?.cancel()
39 | job = coroutineScope.launch(Dispatchers.IO) {
40 | timerUseCase.timerFlow(delay).collect {
41 | with(coroutineScope) { tickHandler(it) }
42 | }
43 | }
44 | }
45 |
46 | fun stop() {
47 | job?.cancel()
48 | }
49 | }
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/GetRoomsInfoUseCase.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.useCase
2 |
3 | import band.effective.office.tablet.core.domain.Either
4 | import band.effective.office.tablet.core.domain.ErrorWithData
5 | import band.effective.office.tablet.core.domain.model.RoomInfo
6 | import band.effective.office.tablet.core.domain.repository.LocalRoomRepository
7 |
8 | /**
9 | * Use case for getting information about all rooms.
10 | *
11 | * @property localRoomRepository Repository for local room storage operations
12 | * @property refreshDataUseCase Use case for refreshing room information
13 | */
14 | class GetRoomsInfoUseCase(
15 | private val localRoomRepository: LocalRoomRepository,
16 | private val refreshDataUseCase: RefreshDataUseCase,
17 | ) {
18 | /**
19 | * Gets information about all rooms.
20 | * First tries to get the information from the local repository.
21 | * If the local repository has no data, it refreshes the data from the network repository.
22 | *
23 | * @return Either containing room information or an error with saved data
24 | */
25 | suspend operator fun invoke(): Either>, List> {
26 | val roomInfos = localRoomRepository.getRoomsInfo()
27 | if (roomInfos as? Either.Error != null
28 | && roomInfos.error.saveData.isNullOrEmpty()
29 | ) {
30 | return refreshDataUseCase()
31 | }
32 | return roomInfos
33 | }
34 | }
--------------------------------------------------------------------------------
/backend/feature/user/src/main/kotlin/band/effective/office/backend/feature/user/repository/entity/UserEntity.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.user.repository.entity
2 |
3 | import jakarta.persistence.Column
4 | import jakarta.persistence.Entity
5 | import jakarta.persistence.Id
6 | import jakarta.persistence.Table
7 | import java.time.LocalDateTime
8 | import java.util.UUID
9 |
10 | /**
11 | * JPA entity representing a user in the database.
12 | */
13 | @Entity
14 | @Table(name = "users")
15 | class UserEntity(
16 | @Id
17 | val id: UUID = UUID.randomUUID(),
18 |
19 | @Column(nullable = false, unique = false, length = 255)
20 | val username: String,
21 |
22 | @Column(nullable = false, unique = true, length = 255)
23 | val email: String,
24 |
25 | @Column(name = "first_name", nullable = false, length = 255)
26 | val firstName: String,
27 |
28 | @Column(name = "last_name", nullable = false, length = 255)
29 | val lastName: String,
30 |
31 | @Column(name = "created_at", nullable = false)
32 | val createdAt: LocalDateTime = LocalDateTime.now(),
33 |
34 | @Column(name = "updated_at", nullable = false)
35 | val updatedAt: LocalDateTime = LocalDateTime.now(),
36 |
37 | @Column(nullable = false)
38 | val active: Boolean = true,
39 |
40 | @Column(nullable = false, length = 50)
41 | val role: String = "",
42 |
43 | @Column(name = "avatar_url", nullable = false, length = 255)
44 | val avatarUrl: String = "",
45 |
46 | @Column(nullable = false, length = 50)
47 | val tag: String = ""
48 | )
49 |
--------------------------------------------------------------------------------
/clients/tablet/core/ui/src/commonMain/composeResources/drawable/quantity.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/util/LocalDateTimeUtils.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.domain.util
2 |
3 | import kotlin.time.Duration
4 | import kotlin.time.Duration.Companion.minutes
5 | import kotlinx.datetime.Instant
6 | import kotlinx.datetime.Clock
7 | import kotlinx.datetime.LocalDate
8 | import kotlinx.datetime.LocalDateTime
9 | import kotlinx.datetime.TimeZone
10 | import kotlinx.datetime.toInstant
11 | import kotlinx.datetime.toLocalDateTime
12 |
13 | val defaultTimeZone = TimeZone.currentSystemDefault()
14 |
15 | val currentLocalDateTime: LocalDateTime get() = Clock.System.now().toLocalDateTime(defaultTimeZone)
16 | val currentLocalDate: LocalDate get() = Clock.System.now().toLocalDateTime(defaultTimeZone).date
17 | val currentInstant: Instant get() = Clock.System.now().toLocalDateTime(defaultTimeZone).asInstant
18 |
19 | fun roundUpToNextQuarter(dateTime: LocalDateTime): LocalDateTime {
20 | val minutes = dateTime.minute
21 | val remainder = minutes % 15
22 | val addMinutes = if (remainder == 0) 0 else 15 - remainder
23 |
24 | return dateTime.toInstant(defaultTimeZone)
25 | .plus(addMinutes.minutes)
26 | .toLocalDateTime(defaultTimeZone)
27 | .cropSeconds()
28 | }
29 |
30 | val LocalDateTime.asInstant get() = toInstant(defaultTimeZone)
31 | val Instant.asLocalDateTime get() = toLocalDateTime(defaultTimeZone)
32 |
33 | fun LocalDateTime.plus(duration: Duration) = asInstant.plus(duration).asLocalDateTime
34 | fun LocalDateTime.minus(duration: Duration) = asInstant.minus(duration).asLocalDateTime
35 |
--------------------------------------------------------------------------------
/backend/feature/booking/core/src/main/kotlin/band/effective/office/backend/feature/booking/core/config/CalendarProviderConfig.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.booking.core.config
2 |
3 | import band.effective.office.backend.feature.booking.core.domain.CalendarProvider
4 | import org.springframework.beans.factory.annotation.Qualifier
5 | import org.springframework.context.annotation.Bean
6 | import org.springframework.context.annotation.Configuration
7 | import org.springframework.context.annotation.Primary
8 |
9 | /**
10 | * Configuration for selecting the appropriate CalendarProvider based on the configuration.
11 | */
12 | @Configuration
13 | class CalendarProviderConfig {
14 |
15 | /**
16 | * Provides the primary CalendarProvider bean based on the configuration.
17 | * This bean will be injected into the BookingService.
18 | *
19 | * @param googleCalendarProvider The Google Calendar provider (if available)
20 | * @param dummyCalendarProvider The dummy calendar provider (if available)
21 | * @return The selected CalendarProvider
22 | */
23 | @Bean
24 | @Primary
25 | fun calendarProvider(
26 | @Qualifier("googleCalendarProvider") googleCalendarProvider: CalendarProvider?,
27 | @Qualifier("dummyCalendarProvider") dummyCalendarProvider: CalendarProvider?
28 | ): CalendarProvider {
29 | // Use the Google Calendar provider if available, otherwise use the dummy provider
30 | return googleCalendarProvider ?: dummyCalendarProvider
31 | ?: throw IllegalStateException("No CalendarProvider implementation available")
32 | }
33 | }
--------------------------------------------------------------------------------
/clients/tablet/core/data/src/commonMain/kotlin/band/effective/office/tablet/core/data/api/UserApi.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.core.data.api
2 |
3 | import band.effective.office.tablet.core.data.dto.user.UserDTO
4 | import band.effective.office.tablet.core.domain.Either
5 | import band.effective.office.tablet.core.domain.ErrorResponse
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | /**
10 | * Interface for user-related operations
11 | */
12 | interface UserApi {
13 | /**
14 | * Get user by id
15 | * @param id user id
16 | * @return User info
17 | */
18 | suspend fun getUser(id: String): Either
19 |
20 | /**
21 | * Get all users
22 | * @param tag user type (employee or guest)
23 | * @return Users list
24 | */
25 | suspend fun getUsers(tag: String): Either>
26 |
27 | /**
28 | * Update information about user
29 | * @param user new user model
30 | * @return New model from database
31 | */
32 | suspend fun updateUser(user: UserDTO): Either
33 |
34 | /**
35 | * Get user by email
36 | * @param email user email
37 | * @return User info
38 | */
39 | suspend fun getUserByEmail(email: String): Either
40 |
41 | /**
42 | * Subscribe on organizers list updates
43 | * @param scope CoroutineScope for collect updates
44 | * @return Flow with updates
45 | */
46 | fun subscribeOnOrganizersList(scope: CoroutineScope): Flow>>
47 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Gradle ###
2 | .gradle
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**/build/
6 | !**/src/test/**/build/
7 |
8 | ### IntelliJ IDEA ###
9 | .idea/
10 | *.iws
11 | *.iml
12 | *.ipr
13 | out/
14 | !**/src/main/**/out/
15 | !**/src/test/**/out/
16 |
17 | ### Eclipse ###
18 | .apt_generated
19 | .classpath
20 | .factorypath
21 | .project
22 | .settings
23 | .springBeans
24 | .sts4-cache
25 | bin/
26 | !**/src/main/**/bin/
27 | !**/src/test/**/bin/
28 |
29 | ### NetBeans ###
30 | /nbproject/private/
31 | /nbbuild/
32 | /dist/
33 | /nbdist/
34 | /.nb-gradle/
35 |
36 | ### VS Code ###
37 | .vscode/
38 |
39 | ### Mac OS ###
40 | .DS_Store
41 |
42 | ### Kotlin ###
43 | .kotlin/
44 |
45 | ### Logs ###
46 | *.log
47 | logs/
48 |
49 | ### Local configuration ###
50 | **/application-local.yml
51 | **/application-local-dev.yml
52 | **/application-local.properties
53 |
54 | ### Docker ###
55 | backend/app/src/main/resources/.env
56 | docker-compose.override.yml
57 |
58 | ### Coverage reports ###
59 | *.coverage
60 | htmlcov/
61 | .coverage.*
62 | coverage/
63 | .nyc_output/
64 |
65 | ### Temporary files ###
66 | *.tmp
67 | *.swp
68 | *.swo
69 | *~
70 |
71 | ### Database ###
72 | *.db
73 | *.sqlite
74 | *.sqlite3
75 |
76 | /backend/app/src/main/resources/.env.properties
77 | /backend/app/src/main/resources/google-credentials.json
78 | /oldBackendExample
79 | /backend/app/src/main/resources/firebase-credentials.json
80 | /keystore/
81 | /oldProject/
82 | /clients/tablet/composeApp/google-services.json
83 | /local.properties
84 | /deploy/dev/.env
85 | /deploy/dev/*.jar
86 | /deploy/prod/.env
87 | /deploy/prod/*.jar
88 |
--------------------------------------------------------------------------------
/backend/feature/notifications/src/main/kotlin/band/effective/office/backend/feature/notifications/service/FcmNotificationSender.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.notifications.service
2 |
3 | import com.google.firebase.messaging.FirebaseMessaging
4 | import com.google.firebase.messaging.Message
5 | import org.slf4j.LoggerFactory
6 |
7 | /**
8 | * Class for sending Firebase cloud messages
9 | */
10 | class FcmNotificationSender(
11 | private val fcm: FirebaseMessaging,
12 | ) : INotificationSender {
13 | private val logger = LoggerFactory.getLogger(FcmNotificationSender::class.java)
14 |
15 | /**
16 | * Sends empty FCM message on topic
17 | */
18 | override fun sendEmptyMessage(topic: String) {
19 | logger.info("Sending an FCM message on $topic topic")
20 | val msg: Message = Message.builder()
21 | .setTopic(topic)
22 | .putData("message", "$topic was changed")
23 | .build()
24 | fcm.send(msg)
25 | }
26 |
27 | override fun sendDataMessage(topic: String, data: Map) {
28 | logger.info("Sending data FCM message on $topic topic: $data")
29 | val msg = Message.builder()
30 | .setTopic(topic)
31 | .putAllData(data)
32 | .build()
33 | try {
34 | val messageId = fcm.send(msg)
35 | logger.info("Successfully sent data message to topic: $topic with data: $data, messageId: $messageId")
36 | } catch (e: Exception) {
37 | logger.error("Failed to send data message to topic $topic: ${e.message}")
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/components/uiComponent/FreeRoomInfoComponent.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.feature.main.components.uiComponent
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import band.effective.office.tablet.core.ui.theme.LocalCustomColorsPalette
8 | import band.effective.office.tablet.core.ui.theme.h5
9 | import band.effective.office.tablet.core.ui.theme.roomInfoColor
10 | import band.effective.office.tablet.feature.main.Res
11 | import band.effective.office.tablet.feature.main.data_not_upd
12 | import band.effective.office.tablet.feature.main.free_now
13 | import org.jetbrains.compose.resources.stringResource
14 |
15 | @Composable
16 | fun FreeRoomInfoComponent(
17 | modifier: Modifier = Modifier,
18 | name: String,
19 | capacity: Int,
20 | isHaveTv: Boolean,
21 | electricSocketCount: Int,
22 | isError: Boolean,
23 | ) {
24 | CommonRoomInfoComponent(
25 | modifier = modifier,
26 | name = name,
27 | capacity = capacity,
28 | isHaveTv = isHaveTv,
29 | electricSocketCount = electricSocketCount,
30 | backgroundColor = LocalCustomColorsPalette.current.freeStatus,
31 | isError = isError
32 | ) {
33 | Text(
34 | text = stringResource(if (isError) Res.string.data_not_upd else Res.string.free_now),
35 | style = MaterialTheme.typography.h5,
36 | color = roomInfoColor,
37 | )
38 | }
39 | }
--------------------------------------------------------------------------------
/clients/tablet/composeApp/src/iosMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.tablet.time
2 |
3 | import band.effective.office.tablet.feature.main.domain.CurrentTimeHolder
4 | import kotlinx.coroutines.flow.StateFlow
5 | import kotlinx.datetime.Clock
6 | import kotlinx.datetime.LocalDateTime
7 | import kotlinx.datetime.TimeZone
8 | import kotlinx.datetime.toLocalDateTime
9 | import platform.Foundation.NSNotificationCenter
10 | import platform.Foundation.NSDefaultRunLoopMode
11 | import platform.Foundation.NSRunLoop
12 | import platform.Foundation.NSDate
13 | import platform.Foundation.NSTimer
14 | import platform.Foundation.timeIntervalSince1970
15 |
16 | /**
17 | * iOS implementation of TimeReceiver that uses a timer to update the current time.
18 | */
19 | actual class TimeReceiver {
20 | actual val currentTime: StateFlow = CurrentTimeHolder.currentTime
21 |
22 | private var timer: NSTimer? = null
23 |
24 | init {
25 | // Update time every minute
26 | timer = NSTimer.scheduledTimerWithTimeInterval(
27 | 60.0, // 60 seconds
28 | true, // repeats
29 | {
30 | CurrentTimeHolder.updateTime(getCurrentTime())
31 | }
32 | )
33 |
34 | // Add timer to the main run loop
35 | NSRunLoop.mainRunLoop.addTimer(timer!!, NSDefaultRunLoopMode)
36 | }
37 |
38 | /**
39 | * Gets the current time as a LocalDateTime.
40 | */
41 | private fun getCurrentTime(): LocalDateTime {
42 | return Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/backend/feature/calendar-subscription/src/main/kotlin/band/effective/office/backend/feature/calendar/subscription/repository/mapper/ChannelMapper.kt:
--------------------------------------------------------------------------------
1 | package band.effective.office.backend.feature.calendar.subscription.repository.mapper
2 |
3 | import band.effective.office.backend.feature.calendar.subscription.repository.entity.ChannelEntity
4 | import com.google.api.services.calendar.model.Channel
5 |
6 | /**
7 | * Mapper for converting between Google Calendar Channel and ChannelEntity.
8 | */
9 | object ChannelMapper {
10 | /**
11 | * Converts a Google Calendar Channel to a ChannelEntity.
12 | *
13 | * @param channel The Google Calendar Channel to convert
14 | * @param calendarId The ID of the calendar associated with the channel
15 | * @return The converted ChannelEntity
16 | */
17 | fun toEntity(channel: Channel, calendarId: String, address: String): ChannelEntity {
18 | return ChannelEntity(
19 | calendarId = calendarId,
20 | channelId = channel.id,
21 | resourceId = channel.resourceId,
22 | type = "web_hook",
23 | address = address,
24 | )
25 | }
26 |
27 | /**
28 | * Converts a ChannelEntity to a Google Calendar Channel.
29 | *
30 | * @param entity The ChannelEntity to convert
31 | * @return The converted Google Calendar Channel
32 | */
33 | fun toChannel(entity: ChannelEntity): Channel {
34 | return Channel().apply {
35 | id = entity.channelId
36 | resourceId = entity.resourceId
37 | type = entity.type
38 | address = entity.address
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/backend/feature/user/README.md:
--------------------------------------------------------------------------------
1 | # User Module
2 |
3 | ## Overview
4 | The User Module provides functionality for managing users within the Effective Office system. It enables creating, retrieving, updating, and deleting user accounts, as well as managing user-related information.
5 |
6 | ## Features
7 | - User account management (CRUD operations)
8 | - User authentication integration
9 | - Active user filtering
10 | - RESTful API for user operations
11 |
12 | ## Architecture
13 | The module follows a layered architecture:
14 |
15 | ```
16 | user/
17 | ├── config/ # Configuration classes
18 | ├── controller/ # REST controllers
19 | ├── dto/ # Data Transfer Objects
20 | ├── repository/ # Data access layer
21 | ├── service/ # Business logic services
22 | ```
23 |
24 | ## Key Components
25 |
26 | ### Core Components
27 | - **UserController**: REST API endpoints for user operations
28 | - **UserService**: Business logic for user management
29 | - **UserRepository**: Data access for user entities
30 |
31 | ### API Endpoints
32 | The module exposes the following REST endpoints:
33 |
34 | - `GET /v1/users`: Get all users
35 | - `GET /v1/users/{id}`: Get user by ID
36 | - `GET /v1/users/by-username/{username}`: Get user by username
37 | - `GET /v1/users/active`: Get all active users
38 | - `POST /v1/users`: Create a new user
39 | - `PUT /v1/users/{id}`: Update an existing user
40 | - `DELETE /v1/users/{id}`: Delete a user
41 |
42 | ## Integration
43 | The User module integrates with:
44 | - Authentication and authorization module
45 | - Notification module for user-related notifications
46 | - Workspace module for user workspace assignments
47 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "effective-office"
2 |
3 | pluginManagement {
4 | includeBuild("build-logic")
5 | repositories {
6 | google()
7 | gradlePluginPortal()
8 | mavenCentral()
9 | }
10 | }
11 |
12 | dependencyResolutionManagement {
13 | repositories {
14 | google()
15 | mavenCentral()
16 | maven(url = "https://maven.pkg.jetbrains.space/public/p/compose/dev")
17 | maven(url = "https://androidx.dev/storage/compose-compiler/repository")
18 | }
19 |
20 | }
21 | plugins {
22 | //https://github.com/JetBrains/compose-hot-reload?tab=readme-ov-file#set-up-automatic-provisioning-of-the-jetbrains-runtime-jbr-via-gradle
23 | id("org.gradle.toolchains.foojay-resolver-convention").version("0.10.0")
24 | }
25 |
26 |
27 | include(":backend")
28 | include(
29 | "backend:app",
30 | "backend:core:domain",
31 | "backend:core:repository",
32 | "backend:core:data",
33 | "backend:feature",
34 | "backend:feature:authorization",
35 | "backend:feature:user",
36 | "backend:feature:booking:core",
37 | "backend:feature:booking:calendar:google",
38 | "backend:feature:booking:calendar:dummy",
39 | "backend:feature:workspace",
40 | "backend:feature:calendar-subscription",
41 | "backend:feature:notifications",
42 |
43 | "clients:tablet:composeApp",
44 | "clients:tablet:core:ui",
45 | "clients:tablet:core:domain",
46 | "clients:tablet:core:data",
47 | "clients:tablet:feature:main",
48 | "clients:tablet:feature:settings",
49 | "clients:tablet:feature:bookingEditor",
50 | "clients:tablet:feature:fastBooking",
51 | "clients:tablet:feature:slot",
52 | )
53 |
--------------------------------------------------------------------------------