├── 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 | --------------------------------------------------------------------------------