diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9114f52..5d6ed47 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -98,6 +98,14 @@ dependencies { implementation("androidx.room:room-ktx:$room_version") implementation("androidx.room:room-paging:$room_version") + // retrofit + val retrofitVersion = "2.9.0" + implementation("com.squareup.retrofit2:retrofit:$retrofitVersion") + implementation("com.squareup.okhttp3:logging-interceptor:4.11.0") + implementation("androidx.paging:paging-compose:3.2.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") + // Tests testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") diff --git a/app/src/main/java/com/example/flowershopapp/API/Mediator/BouquetRemoteMediator.kt b/app/src/main/java/com/example/flowershopapp/API/Mediator/BouquetRemoteMediator.kt new file mode 100644 index 0000000..3c44b9b --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Mediator/BouquetRemoteMediator.kt @@ -0,0 +1,111 @@ +package com.example.flowershopapp.API.Mediator + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.flowershopapp.API.Model.toBouquet +import com.example.flowershopapp.API.MyServerService +import com.example.flowershopapp.API.Repository.RestOrderBouquetRepository +import com.example.flowershopapp.Database.AppDatabase +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys +import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository +import com.example.flowershopapp.Entities.Model.Bouquet +import com.example.flowershopapp.Entities.Repository.Bouquet.OfflineBouquetRepository +import retrofit2.HttpException +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class BouquetRemoteMediator( + private val service: MyServerService, + private val dbBouquetRepository: OfflineBouquetRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val orderBouquetRestRepository: RestOrderBouquetRepository, + private val database: AppDatabase +) : RemoteMediator() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + val page = when (loadType) { + LoadType.REFRESH -> { + val remoteKeys = getRemoteKeyClosestToCurrentPosition(state) + remoteKeys?.nextKey?.minus(1) ?: 1 + } + + LoadType.PREPEND -> { + val remoteKeys = getRemoteKeyForFirstItem(state) + remoteKeys?.prevKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + + LoadType.APPEND -> { + val remoteKeys = getRemoteKeyForLastItem(state) + remoteKeys?.nextKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + } + + try { + val bouquets = service.getBouquets(page, state.config.pageSize).map { it.toBouquet() } + val endOfPaginationReached = bouquets.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.BOUQUET) + dbBouquetRepository.deleteAll() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = bouquets.map { + it.bouquetId?.let { it1 -> + RemoteKeys( + entityId = it1, + type = RemoteKeyType.BOUQUET, + prevKey = prevKey, + nextKey = nextKey + ) + } + } + orderBouquetRestRepository.getAll() + dbRemoteKeyRepository.createRemoteKeys(keys) + dbBouquetRepository.insertBouquets(bouquets) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return MediatorResult.Error(exception) + } catch (exception: HttpException) { + return MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { bouquet -> + bouquet.bouquetId?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.BOUQUET) } + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { bouquet -> + bouquet.bouquetId?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.BOUQUET) } + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.bouquetId?.let { bouquetUid -> + dbRemoteKeyRepository.getAllRemoteKeys(bouquetUid, RemoteKeyType.BOUQUET) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Mediator/OrderRemoteMediator.kt b/app/src/main/java/com/example/flowershopapp/API/Mediator/OrderRemoteMediator.kt new file mode 100644 index 0000000..06da3bc --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Mediator/OrderRemoteMediator.kt @@ -0,0 +1,108 @@ +package com.example.flowershopapp.API.Mediator + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.flowershopapp.API.Model.toOrder +import com.example.flowershopapp.API.MyServerService +import com.example.flowershopapp.Database.AppDatabase +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys +import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository +import com.example.flowershopapp.Entities.Model.Order +import com.example.flowershopapp.Entities.Repository.Order.OfflineOrderRepository +import retrofit2.HttpException +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class OrderRemoteMediator( + private val service: MyServerService, + private val dbOrderRepository: OfflineOrderRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val database: AppDatabase +) : RemoteMediator() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + val page = when (loadType) { + LoadType.REFRESH -> { + val remoteKeys = getRemoteKeyClosestToCurrentPosition(state) + remoteKeys?.nextKey?.minus(1) ?: 1 + } + + LoadType.PREPEND -> { + val remoteKeys = getRemoteKeyForFirstItem(state) + remoteKeys?.prevKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + + LoadType.APPEND -> { + val remoteKeys = getRemoteKeyForLastItem(state) + remoteKeys?.nextKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + } + + try { + val Orders = service.getOrders(page, state.config.pageSize).map { it.toOrder() } + val endOfPaginationReached = Orders.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER) + dbOrderRepository.deleteAll() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = Orders.map { + it.orderId?.let { it1 -> + RemoteKeys( + entityId = it1, + type = RemoteKeyType.ORDER, + prevKey = prevKey, + nextKey = nextKey + ) + } + } + dbRemoteKeyRepository.createRemoteKeys(keys) + dbOrderRepository.insertOrders(Orders) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return MediatorResult.Error(exception) + } catch (exception: HttpException) { + return MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { Order -> + Order.orderId?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.ORDER) } + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { Order -> + Order.orderId?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.ORDER) } + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.orderId?.let { OrderUid -> + dbRemoteKeyRepository.getAllRemoteKeys(OrderUid, RemoteKeyType.ORDER) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Mediator/UserRemoteMediator.kt b/app/src/main/java/com/example/flowershopapp/API/Mediator/UserRemoteMediator.kt new file mode 100644 index 0000000..5d0ebb0 --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Mediator/UserRemoteMediator.kt @@ -0,0 +1,112 @@ +package com.example.flowershopapp.API.Mediator + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.flowershopapp.API.Model.toUser +import com.example.flowershopapp.API.MyServerService +import com.example.flowershopapp.API.Repository.RestOrderBouquetRepository +import com.example.flowershopapp.API.Repository.RestUserOrderRepository +import com.example.flowershopapp.Database.AppDatabase +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys +import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository +import com.example.flowershopapp.Entities.Model.User +import com.example.flowershopapp.Entities.Repository.User.OfflineUserRepository +import retrofit2.HttpException +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class UserRemoteMediator( + private val service: MyServerService, + private val dbUserRepository: OfflineUserRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val userOrderRestRepository: RestUserOrderRepository, + private val database: AppDatabase +) : RemoteMediator() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + val page = when (loadType) { + LoadType.REFRESH -> { + val remoteKeys = getRemoteKeyClosestToCurrentPosition(state) + remoteKeys?.nextKey?.minus(1) ?: 1 + } + + LoadType.PREPEND -> { + val remoteKeys = getRemoteKeyForFirstItem(state) + remoteKeys?.prevKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + + LoadType.APPEND -> { + val remoteKeys = getRemoteKeyForLastItem(state) + remoteKeys?.nextKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + } + + try { + val Users = service.getUsers().map { it.toUser() } + val endOfPaginationReached = Users.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER) + dbUserRepository.deleteAll() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = Users.map { + it.userId?.let { it1 -> + RemoteKeys( + entityId = it1, + type = RemoteKeyType.ORDER, + prevKey = prevKey, + nextKey = nextKey + ) + } + } + userOrderRestRepository.getAll() + dbRemoteKeyRepository.createRemoteKeys(keys) + dbUserRepository.insertUsers(Users) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return MediatorResult.Error(exception) + } catch (exception: HttpException) { + return MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { User -> + User.userId?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.ORDER) } + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { User -> + User.userId?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.ORDER) } + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.userId?.let { UserUid -> + dbRemoteKeyRepository.getAllRemoteKeys(UserUid, RemoteKeyType.ORDER) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Model/BouquetRemote.kt b/app/src/main/java/com/example/flowershopapp/API/Model/BouquetRemote.kt new file mode 100644 index 0000000..7c30587 --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Model/BouquetRemote.kt @@ -0,0 +1,29 @@ +package com.example.flowershopapp.API.Model + +import com.example.flowershopapp.Entities.Model.Bouquet +import kotlinx.serialization.Serializable + +@Serializable +data class BouquetRemote( + val bouquetId: Int? = 0, + val name: String = "", + val quantityOfFlowers: Int = 0, + val price: Int = 0, + val image: ByteArray? = null, +) + +fun BouquetRemote.toBouquet(): Bouquet = Bouquet( + bouquetId, + name, + quantityOfFlowers, + price, + image +) + +fun Bouquet.toBouquetRemote(): BouquetRemote = BouquetRemote( + bouquetId, + name, + quantityOfFlowers, + price, + image +) \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Model/OrderBouquetRemote.kt b/app/src/main/java/com/example/flowershopapp/API/Model/OrderBouquetRemote.kt new file mode 100644 index 0000000..c5bc64e --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Model/OrderBouquetRemote.kt @@ -0,0 +1,24 @@ +package com.example.flowershopapp.API.Model + +import com.example.flowershopapp.Entities.Model.Bouquet +import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef +import com.example.flowershopapp.Entities.Model.OrdersWithBouquets +import kotlinx.serialization.Serializable + +@Serializable +data class OrderBouquetRemote( + val order: OrderRemote = OrderRemote(), + val bouquet: List = listOf() +) + +fun OrderBouquetRemote.toOrdersWithBouquets(): OrdersWithBouquets { + val convertedOrder = this.order.toOrder() + val convertedBouquets = this.bouquet.map { it.toBouquet() } + return OrdersWithBouquets(convertedOrder, convertedBouquets) +} + +fun OrdersWithBouquets.toOrderBouquetRemote(): OrderBouquetRemote { + val convertedOrder = this.order.toOrderRemote() + val convertedBouquets = this.bouquets.map { it.toBouquetRemote() } + return OrderBouquetRemote(convertedOrder, convertedBouquets) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Model/OrderRemote.kt b/app/src/main/java/com/example/flowershopapp/API/Model/OrderRemote.kt new file mode 100644 index 0000000..2dc9dcf --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Model/OrderRemote.kt @@ -0,0 +1,23 @@ +package com.example.flowershopapp.API.Model + +import com.example.flowershopapp.Entities.Model.Order +import kotlinx.serialization.Serializable + +@Serializable +data class OrderRemote( + val orderId: Int? = 0, + val date: String = "", + val sum: Int = 0, +) + +fun OrderRemote.toOrder(): Order = Order( + orderId, + date, + sum +) + +fun Order.toOrderRemote(): OrderRemote = OrderRemote( + orderId, + date, + sum +) \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Model/UserOrderRemote.kt b/app/src/main/java/com/example/flowershopapp/API/Model/UserOrderRemote.kt new file mode 100644 index 0000000..459c8ed --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Model/UserOrderRemote.kt @@ -0,0 +1,23 @@ +package com.example.flowershopapp.API.Model + +import com.example.flowershopapp.Entities.Model.UserOrderCrossRef +import com.example.flowershopapp.Entities.Model.UsersWithOrders +import kotlinx.serialization.Serializable + +@Serializable +data class UserOrderRemote( + val user: UserRemote = UserRemote(), + val order: List = listOf() +) + +fun UserOrderRemote.toUserOrder(): UsersWithOrders{ + val convertedUser = this.user.toUser() + val convertedOrders = this.order.map { it.toOrder() } + return UsersWithOrders(convertedUser, convertedOrders) +} + +fun UsersWithOrders.toUserOrderRemote(): UserOrderRemote{ + val convertedUser = this.user.toUserRemote() + val convertedOrdesr = this.orders.map { it.toOrderRemote() } + return UserOrderRemote(convertedUser, convertedOrdesr) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Model/UserRemote.kt b/app/src/main/java/com/example/flowershopapp/API/Model/UserRemote.kt new file mode 100644 index 0000000..ca42844 --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Model/UserRemote.kt @@ -0,0 +1,29 @@ +package com.example.flowershopapp.API.Model + +import com.example.flowershopapp.Entities.Model.User +import kotlinx.serialization.Serializable + +@Serializable +data class UserRemote( + val userId: Int? = 0, + val userName: String = "", + val dateOfBirth: String = "", + val phoneNumber: String = "", + val password: String = "" +) + +fun UserRemote.toUser(): User = User( + userId, + userName, + dateOfBirth, + phoneNumber, + password +) + +fun User.toUserRemote(): UserRemote = UserRemote( + userId, + userName, + dateOfBirth, + phoneNumber, + password +) \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/MyServiceService.kt b/app/src/main/java/com/example/flowershopapp/API/MyServiceService.kt new file mode 100644 index 0000000..90f0c60 --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/MyServiceService.kt @@ -0,0 +1,170 @@ +package com.example.flowershopapp.API + +import com.example.flowershopapp.API.Model.BouquetRemote +import com.example.flowershopapp.API.Model.OrderBouquetRemote +import com.example.flowershopapp.API.Model.OrderRemote +import com.example.flowershopapp.API.Model.UserOrderRemote +import com.example.flowershopapp.API.Model.UserRemote +import com.example.flowershopapp.Entities.Model.UsersWithOrders +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.http.Body +import retrofit2.http.DELETE +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path +import retrofit2.http.Query + +interface MyServerService { + @GET("Users") + suspend fun getUsers(): List + + @GET("Users/{name}") + suspend fun getUser( + @Path("name") name: String, + ): UserRemote + + @GET("Users/{name}") + suspend fun getUserWithOrders( + @Path("name") name: String, + ): UserOrderRemote + + @GET("Users/{id}") + suspend fun getUsersWithOrders(): List + + @POST("Users") + suspend fun createUser( + @Body User: UserRemote, + ): UserRemote + + @PUT("Users/{id}") + suspend fun updateUser( + @Path("id") id: Int, + @Body User: UserRemote, + ): UserRemote + @DELETE("Users/{id}") + suspend fun deleteUser( + @Path("id") id: Int, + ): UserRemote + @GET("Orders") + suspend fun getOrders( + @Query("_page") page: Int, + @Query("_limit") limit: Int, + ): List + + @GET("Orders/{id}") + suspend fun getOrder( + @Path("id") id: Int, + ): OrderRemote + + @GET("Orders/{id}") + suspend fun getOrderWithBouquets( + @Path("id") id: Int, + ): OrderBouquetRemote + + @GET("Orders/{id}") + suspend fun getOrdersWithBouquets(): List + + @POST("Orders") + suspend fun createOrder( + @Body Order: OrderRemote, + ): OrderRemote + + @PUT("Orders/{id}") + suspend fun updateOrder( + @Path("id") id: Int, + @Body Order: OrderRemote, + ): OrderRemote + + @DELETE("Orders/{id}") + suspend fun deleteOrder( + @Path("id") id: Int, + ): OrderRemote + @GET("Bouquets") + suspend fun getBouquets( + @Query("_page") page: Int, + @Query("_limit") limit: Int, + ): List + + @GET("Bouquets/{id}") + suspend fun getBouquet( + @Path("id") id: Int, + ): BouquetRemote + + @POST("Bouquets") + suspend fun createBouquet( + @Body Bouquet: BouquetRemote, + ): BouquetRemote + + @PUT("Bouquets/{id}") + suspend fun updateBouquet( + @Path("id") id: Int, + @Body Bouquet: BouquetRemote, + ): BouquetRemote + + @DELETE("Bouquets/{id}") + suspend fun deleteBouquet( + @Path("id") id: Int, + ): BouquetRemote + + @POST("OrderBouquets") + suspend fun createOrderBouquet( + @Body OrderBouquet: OrderBouquetRemote, + ): OrderBouquetRemote + + @PUT("OrderBouquets/{id}") + suspend fun updateOrderBouquet( + @Path("id") id: Int, + @Body OrderBouquet: OrderBouquetRemote, + ): OrderBouquetRemote + + @DELETE("OrderBouquets/{id}") + suspend fun deleteOrderBouquet( + @Path("id") id: Int, + ): OrderBouquetRemote + + @POST("UserOrders") + suspend fun createUserOrder( + @Body UserOrder: UserOrderRemote, + ): UserOrderRemote + + @PUT("UserOrders/{id}") + suspend fun updateUserOrder( + @Path("id") id: Int, + @Body UserOrder: UserOrderRemote, + ): UserOrderRemote + + @DELETE("UserOrders/{id}") + suspend fun deleteUserOrder( + @Path("id") id: Int, + ): UserOrderRemote + + companion object { + private const val BASE_URL = "http://10.0.2.2:8079/" + + @Volatile + private var INSTANCE: MyServerService? = null + + fun getInstance(): MyServerService { + return INSTANCE ?: synchronized(this) { + val logger = HttpLoggingInterceptor() + logger.level = HttpLoggingInterceptor.Level.BASIC + val client = OkHttpClient.Builder() + .addInterceptor(logger) + .build() + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaType())) + .build() + .create(MyServerService::class.java) + .also { INSTANCE = it } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Repository/RestBouquetRepository.kt b/app/src/main/java/com/example/flowershopapp/API/Repository/RestBouquetRepository.kt new file mode 100644 index 0000000..0230590 --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Repository/RestBouquetRepository.kt @@ -0,0 +1,63 @@ +package com.example.flowershopapp.API.Repository + +import android.util.Log +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.flowershopapp.API.Mediator.BouquetRemoteMediator +import com.example.flowershopapp.API.Model.toBouquet +import com.example.flowershopapp.API.Model.toBouquetRemote +import com.example.flowershopapp.API.MyServerService +import com.example.flowershopapp.Database.AppContainer +import com.example.flowershopapp.Database.AppDatabase +import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository +import com.example.flowershopapp.Entities.Model.Bouquet +import com.example.flowershopapp.Entities.Repository.Bouquet.BouquetRepository +import com.example.flowershopapp.Entities.Repository.Bouquet.OfflineBouquetRepository +import kotlinx.coroutines.flow.Flow + +class RestBouquetRepository( + private val service: MyServerService, + private val dbBouquetRepository: OfflineBouquetRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val orderBouquetRestRepository: RestOrderBouquetRepository, + private val database: AppDatabase +) : BouquetRepository { + override fun getAll(): Flow> { + Log.d(RestBouquetRepository::class.simpleName, "Get Bouquets") + + val pagingSourceFactory = { dbBouquetRepository.getAllBouquetsPagingSource() } + + @OptIn(ExperimentalPagingApi::class) + return Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + remoteMediator = BouquetRemoteMediator( + service, + dbBouquetRepository, + dbRemoteKeyRepository, + orderBouquetRestRepository, + database + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } + + override suspend fun getBouquet(uid: Int): Bouquet = + service.getBouquet(uid).toBouquet() + + override suspend fun insert(Bouquet: Bouquet) { + service.createBouquet(Bouquet.toBouquetRemote()).toBouquet() + } + + override suspend fun update(Bouquet: Bouquet) { + Bouquet.bouquetId?.let { service.updateBouquet(it, Bouquet.toBouquetRemote()).toBouquet() } + } + + override suspend fun delete(Bouquet: Bouquet) { + Bouquet.bouquetId?.let { service.deleteBouquet(it).toBouquet() } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Repository/RestOrderBouquetRepository.kt b/app/src/main/java/com/example/flowershopapp/API/Repository/RestOrderBouquetRepository.kt new file mode 100644 index 0000000..6022a5f --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Repository/RestOrderBouquetRepository.kt @@ -0,0 +1,40 @@ +package com.example.flowershopapp.API.Repository + +import android.util.Log +import com.example.flowershopapp.API.Model.toOrderBouquet +import com.example.flowershopapp.API.Model.toOrderBouquetRemote +import com.example.flowershopapp.API.MyServerService +import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef +import com.example.flowershopapp.Entities.Model.OrdersWithBouquets +import com.example.flowershopapp.Entities.Repository.OrderBouquets.OfflineOrdersWithBouquetsRepositoryRepository +import com.example.flowershopapp.Entities.Repository.OrderBouquets.OrdersWithBouquetsRepository + +class RestOrderBouquetRepository( + private val service: MyServerService, + private val dbOrderBouquetRepository: OfflineOrdersWithBouquetsRepositoryRepository, +): OrdersWithBouquetsRepository { + override suspend fun getAll(): List { + Log.d(RestOrderBouquetRepository::class.simpleName, "Get OrderBouquets") + + val existOrderBouquets = dbOrderBouquetRepository.getAll().associateBy { it.orderId }.toMutableMap() + + service.getOrdersWithBouquets() + .map { it.toOrderBouquet() } + .forEach { orderBouquet -> + val existOrderBouquet = existOrderBouquets[orderBouquet.orderId] + if (existOrderBouquet == null) { + dbOrderBouquetRepository.insert(orderBouquet) + } + existOrderBouquets[orderBouquet.orderId] = orderBouquet + } + + return existOrderBouquets.map { it.value }.sortedBy { it.orderId } + } + override suspend fun insert(orderBouquet: OrderBouquetCrossRef) { + service.createOrderBouquet(orderBouquet.toOrderBouquetRemote()).toOrderBouquet() + } + + override suspend fun delete(orderBouquet: OrderBouquetCrossRef) { + service.deleteOrderBouquet(orderBouquet.orderId).toOrderBouquet() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Repository/RestOrderRepository.kt b/app/src/main/java/com/example/flowershopapp/API/Repository/RestOrderRepository.kt new file mode 100644 index 0000000..845573e --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Repository/RestOrderRepository.kt @@ -0,0 +1,4 @@ +package com.example.flowershopapp.API.Repository + +class RestOrderRepository { +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Repository/RestUserOrderRepository.kt b/app/src/main/java/com/example/flowershopapp/API/Repository/RestUserOrderRepository.kt new file mode 100644 index 0000000..a77a2db --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Repository/RestUserOrderRepository.kt @@ -0,0 +1,42 @@ +package com.example.flowershopapp.API.Repository + +import android.util.Log +import com.example.flowershopapp.API.Model.toOrderBouquet +import com.example.flowershopapp.API.Model.toUserOrder +import com.example.flowershopapp.API.Model.toUserOrderRemote +import com.example.flowershopapp.API.MyServerService +import com.example.flowershopapp.Entities.Model.UserOrderCrossRef +import com.example.flowershopapp.Entities.Repository.OrderBouquets.OfflineOrdersWithBouquetsRepositoryRepository +import com.example.flowershopapp.Entities.Repository.OrderBouquets.OrdersWithBouquetsRepository +import com.example.flowershopapp.Entities.Repository.UserOrders.OfflineUsersWithOrdersRepository +import com.example.flowershopapp.Entities.Repository.UserOrders.UsersWithOrdersRepository + +class RestUserOrderRepository( + private val service: MyServerService, + private val dbUserOrderRepository: OfflineUsersWithOrdersRepository, +): UsersWithOrdersRepository { + override suspend fun getAll(): List { + Log.d(RestUserOrderRepository::class.simpleName, "Get UserOrders") + + val existUserOrders = dbUserOrderRepository.getAll().associateBy { it.userId }.toMutableMap() + + service.getUsersWithOrders() + .map { it.toUserOrder() } + .forEach { UserOrder -> + val existUserOrder = existUserOrders[UserOrder.orderId] + if (existUserOrder == null) { + dbUserOrderRepository.insert(UserOrder) + } + existUserOrders[UserOrder.orderId] = UserOrder + } + + return existUserOrders.map { it.value }.sortedBy { it.orderId } + } + override suspend fun insert(UserOrder: UserOrderCrossRef) { + service.createUserOrder(UserOrder.toUserOrderRemote()).toUserOrder() + } + + override suspend fun delete(UserOrder: UserOrderCrossRef) { + service.deleteUserOrder(UserOrder.orderId).toUserOrder() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/API/Repository/RestUserRepository.kt b/app/src/main/java/com/example/flowershopapp/API/Repository/RestUserRepository.kt new file mode 100644 index 0000000..51e833b --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/API/Repository/RestUserRepository.kt @@ -0,0 +1,74 @@ +package com.example.flowershopapp.API.Repository + +import android.util.Log +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.flowershopapp.API.Mediator.UserRemoteMediator +import com.example.flowershopapp.API.Model.toUser +import com.example.flowershopapp.API.Model.toUserOrder +import com.example.flowershopapp.API.Model.toUserRemote +import com.example.flowershopapp.API.MyServerService +import com.example.flowershopapp.Database.AppContainer +import com.example.flowershopapp.Database.AppDatabase +import com.example.flowershopapp.Database.RemoteKeys.Repository.OfflineRemoteKeyRepository +import com.example.flowershopapp.Entities.Model.User +import com.example.flowershopapp.Entities.Model.UsersWithOrders +import com.example.flowershopapp.Entities.Repository.User.UserRepository +import com.example.flowershopapp.Entities.Repository.User.OfflineUserRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +class RestUserRepository( + private val service: MyServerService, + private val dbUserRepository: OfflineUserRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val userOrderRestRepository: RestUserOrderRepository, + private val database: AppDatabase +) : UserRepository { + override fun getAll(): Flow> { + Log.d(RestUserRepository::class.simpleName, "Get Users") + + val pagingSourceFactory = { dbUserRepository.getAll() } + + @OptIn(ExperimentalPagingApi::class) + return Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + remoteMediator = UserRemoteMediator( + service, + dbUserRepository, + dbRemoteKeyRepository, + userOrderRestRepository, + database + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } + + override suspend fun getUser(userName: String): User = + service.getUser(userName).toUser() + + override suspend fun getUserWithOrders(userName: String): Flow { + return flowOf(service.getUserWithOrders(userName).toUserOrder()) + } + + override fun getUsersWithOrders(): Flow> { + TODO("Not yet implemented") + } + + override suspend fun insert(User: User) { + service.createUser(User.toUserRemote()).toUser() + } + + override suspend fun update(User: User) { + User.userId?.let { service.updateUser(it, User.toUserRemote()).toUser() } + } + + override suspend fun delete(User: User) { + User.userId?.let { service.deleteUser(it).toUser() } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Database/AppContainer.kt b/app/src/main/java/com/example/flowershopapp/Database/AppContainer.kt index 5d205b0..08db2aa 100644 --- a/app/src/main/java/com/example/flowershopapp/Database/AppContainer.kt +++ b/app/src/main/java/com/example/flowershopapp/Database/AppContainer.kt @@ -18,6 +18,11 @@ interface AppContainer { val orderRepository: OrderRepository val userOrdersRepository: UsersWithOrdersRepository val orderBouquetsRepository: OrdersWithBouquetsRepository + + companion object { + const val TIMEOUT = 5000L + const val LIMIT = 10 + } } class AppDataContainer(private val context: Context) : AppContainer { override val userRepository: UserRepository by lazy { @@ -36,7 +41,4 @@ class AppDataContainer(private val context: Context) : AppContainer { OfflineOrdersWithBouquetsRepositoryRepository(AppDatabase.getInstance(context).orderWithBouquetsDao()) } - companion object { - const val TIMEOUT = 5000L - } } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/DAO/RemoteKeysDAO.kt b/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/DAO/RemoteKeysDAO.kt new file mode 100644 index 0000000..c96bfda --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/DAO/RemoteKeysDAO.kt @@ -0,0 +1,20 @@ +package com.example.flowershopapp.Database.RemoteKeys.DAO + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys + +@Dao +interface RemoteKeysDao { + @Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type") + suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(remoteKey: List) + + @Query("DELETE FROM remote_keys WHERE type = :type") + suspend fun clearRemoteKeys(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Model/RemoteKeys.kt b/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Model/RemoteKeys.kt new file mode 100644 index 0000000..aa4b79e --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Model/RemoteKeys.kt @@ -0,0 +1,30 @@ +package com.example.flowershopapp.Database.RemoteKeys.Model + +import androidx.room.Entity +import androidx.room.PrimaryKey +import androidx.room.TypeConverter +import androidx.room.TypeConverters +import com.example.flowershopapp.Entities.Model.Bouquet +import com.example.flowershopapp.Entities.Model.Order +import com.example.flowershopapp.Entities.Model.User + +enum class RemoteKeyType(private val type: String) { + BOUQUET(Bouquet::class.simpleName ?: "Bouquet"), + ORDER(Order::class.simpleName ?: "Order"), + USER(User::class.simpleName ?: "User"); + + @TypeConverter + fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value } + + @TypeConverter + fun fromRemoteKeyType(value: RemoteKeyType) = value.type +} + +@Entity(tableName = "remote_keys") +data class RemoteKeys( + @PrimaryKey val entityId: Int, + @TypeConverters(RemoteKeyType::class) + val type: RemoteKeyType, + val prevKey: Int?, + val nextKey: Int? +) \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Repository/OfflineRemoteKeyRepository.kt b/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Repository/OfflineRemoteKeyRepository.kt new file mode 100644 index 0000000..a032d1f --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Repository/OfflineRemoteKeyRepository.kt @@ -0,0 +1,16 @@ +package com.example.flowershopapp.Database.RemoteKeys.Repository + +import com.example.flowershopapp.Database.RemoteKeys.DAO.RemoteKeysDao +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys + +class OfflineRemoteKeyRepository(private val remoteKeysDao: RemoteKeysDao) : RemoteKeyRepository { + override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) = + remoteKeysDao.getRemoteKeys(id, type) + + override suspend fun createRemoteKeys(remoteKeys: List) = + remoteKeysDao.insertAll(remoteKeys) + + override suspend fun deleteRemoteKey(type: RemoteKeyType) = + remoteKeysDao.clearRemoteKeys(type) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Repository/RemoteKeyRepository.kt b/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Repository/RemoteKeyRepository.kt new file mode 100644 index 0000000..27e08fb --- /dev/null +++ b/app/src/main/java/com/example/flowershopapp/Database/RemoteKeys/Repository/RemoteKeyRepository.kt @@ -0,0 +1,10 @@ +package com.example.flowershopapp.Database.RemoteKeys.Repository + +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeyType +import com.example.flowershopapp.Database.RemoteKeys.Model.RemoteKeys + +interface RemoteKeyRepository { + suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys? + suspend fun createRemoteKeys(remoteKeys: List) + suspend fun deleteRemoteKey(type: RemoteKeyType) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/DAO/BouquetDAO.kt b/app/src/main/java/com/example/flowershopapp/Entities/DAO/BouquetDAO.kt index 5fee796..ee6bd80 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/DAO/BouquetDAO.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/DAO/BouquetDAO.kt @@ -1,5 +1,6 @@ package com.example.flowershopapp.Entities.DAO +import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert @@ -13,18 +14,20 @@ import kotlinx.coroutines.flow.Flow @Dao interface BouquetDAO { @Query("select * from bouquets") - fun getAll(): Flow> - + fun getAll(): PagingSource @Query("select * from bouquets where bouquetId = :id") fun getBouquet(id: Int): Bouquet @Insert - suspend fun insert(bouquet: Bouquet) + suspend fun insert(vararg bouquet: Bouquet) @Update suspend fun update(bouquet: Bouquet) @Delete suspend fun delete(bouquet: Bouquet) + + @Query("DELETE FROM bouquets") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/DAO/OrderDAO.kt b/app/src/main/java/com/example/flowershopapp/Entities/DAO/OrderDAO.kt index 8c112ad..5bee186 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/DAO/OrderDAO.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/DAO/OrderDAO.kt @@ -1,5 +1,6 @@ package com.example.flowershopapp.Entities.DAO +import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert @@ -12,7 +13,7 @@ import kotlinx.coroutines.flow.Flow @Dao interface OrderDAO { @Query("select * from orders") - fun getAll(): List + fun getAll(): PagingSource @Query("select * from orders") fun getOrdersWithBouquet(): Flow> @@ -21,11 +22,14 @@ interface OrderDAO { fun getOrderWithBouquet(id: Int): OrdersWithBouquets @Insert - suspend fun insert(order: Order) + suspend fun insert(vararg order: Order) @Update suspend fun update(order: Order) @Delete suspend fun delete(order: Order) + + @Query("DELETE FROM orders") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/DAO/OrdersWithBouquet.kt b/app/src/main/java/com/example/flowershopapp/Entities/DAO/OrdersWithBouquet.kt index f7691e8..779d0b6 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/DAO/OrdersWithBouquet.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/DAO/OrdersWithBouquet.kt @@ -3,19 +3,20 @@ package com.example.flowershopapp.Entities.DAO import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert +import androidx.room.Query import androidx.room.Update import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef import com.example.flowershopapp.Entities.Model.OrdersWithBouquets import com.example.flowershopapp.Entities.Model.UsersWithOrders +import kotlinx.coroutines.flow.Flow @Dao interface OrdersWithBouquet { + @Query("select * from orderbouquetcrossref") + suspend fun getAll() : List @Insert suspend fun insert(order: OrderBouquetCrossRef) - @Update - suspend fun update(order: OrderBouquetCrossRef) - @Delete suspend fun delete(order: OrderBouquetCrossRef) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/DAO/UserDAO.kt b/app/src/main/java/com/example/flowershopapp/Entities/DAO/UserDAO.kt index feb9b5d..bf9f2bf 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/DAO/UserDAO.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/DAO/UserDAO.kt @@ -1,10 +1,12 @@ package com.example.flowershopapp.Entities.DAO +import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.Query import androidx.room.Update +import com.example.flowershopapp.Entities.Model.Order import com.example.flowershopapp.Entities.Model.OrdersWithBouquets import com.example.flowershopapp.Entities.Model.User import com.example.flowershopapp.Entities.Model.UsersWithOrders @@ -13,7 +15,7 @@ import kotlinx.coroutines.flow.Flow @Dao interface UserDAO { @Query("select * from users") - fun getAll(): List + fun getAll(): PagingSource @Query("select * from users where name = :userName") fun getUser(userName: String): User @@ -25,11 +27,13 @@ interface UserDAO { fun getUsersWithOrders(): Flow> @Insert - suspend fun insert(user: User) + suspend fun insert(vararg user: User) @Update suspend fun update(user: User) @Delete suspend fun delete(user: User) + @Query("DELETE FROM users") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/DAO/UsersWithOrders.kt b/app/src/main/java/com/example/flowershopapp/Entities/DAO/UsersWithOrders.kt index 985fbe4..adf9a49 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/DAO/UsersWithOrders.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/DAO/UsersWithOrders.kt @@ -3,18 +3,18 @@ package com.example.flowershopapp.Entities.DAO import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert +import androidx.room.Query import androidx.room.Update +import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef import com.example.flowershopapp.Entities.Model.UserOrderCrossRef import com.example.flowershopapp.Entities.Model.UsersWithOrders @Dao interface UsersWithOrders { + @Query("select * from userordercrossref") + suspend fun getAll() : List @Insert suspend fun insert(user: UserOrderCrossRef) - - @Update - suspend fun update(user: UserOrderCrossRef) - @Delete suspend fun delete(user: UserOrderCrossRef) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/Bouquet/BouquetRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/Bouquet/BouquetRepository.kt index fccdfae..d15e55f 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/Bouquet/BouquetRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/Bouquet/BouquetRepository.kt @@ -1,5 +1,6 @@ package com.example.flowershopapp.Entities.Repository.Bouquet +import androidx.paging.PagingData import androidx.room.Delete import androidx.room.Insert import androidx.room.Query @@ -8,8 +9,8 @@ import com.example.flowershopapp.Entities.Model.Bouquet import kotlinx.coroutines.flow.Flow interface BouquetRepository { - fun getAll(): Flow> - fun getBouquet(id: Int): Bouquet + fun getAll(): Flow> + suspend fun getBouquet(id: Int): Bouquet suspend fun insert(bouquet: Bouquet) suspend fun update(bouquet: Bouquet) suspend fun delete(bouquet: Bouquet) diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/Bouquet/OfflineBouquetRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/Bouquet/OfflineBouquetRepository.kt index 0738dc3..79d3d43 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/Bouquet/OfflineBouquetRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/Bouquet/OfflineBouquetRepository.kt @@ -1,13 +1,28 @@ package com.example.flowershopapp.Entities.Repository.Bouquet +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource +import com.example.flowershopapp.Database.AppContainer import com.example.flowershopapp.Entities.DAO.BouquetDAO import com.example.flowershopapp.Entities.Model.Bouquet import kotlinx.coroutines.flow.Flow class OfflineBouquetRepository(private val bouquetDAO: BouquetDAO) : BouquetRepository { - override fun getAll(): Flow> = bouquetDAO.getAll() - override fun getBouquet(id: Int): Bouquet = bouquetDAO.getBouquet(id) + override fun getAll(): Flow> = Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + pagingSourceFactory = bouquetDAO::getAll + ).flow + override suspend fun getBouquet(id: Int): Bouquet = bouquetDAO.getBouquet(id) override suspend fun insert(bouquet: Bouquet) = bouquetDAO.insert(bouquet) override suspend fun update(bouquet: Bouquet) = bouquetDAO.update(bouquet) override suspend fun delete(bouquet: Bouquet) = bouquetDAO.delete(bouquet) + suspend fun deleteAll() = bouquetDAO.deleteAll() + fun getAllBouquetsPagingSource(): PagingSource = bouquetDAO.getAll() + suspend fun insertBouquets(bouquets: List) = + bouquetDAO.insert(*bouquets.toTypedArray()) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/Order/OfflineOrderRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/Order/OfflineOrderRepository.kt index ed15027..6a8f43c 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/Order/OfflineOrderRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/Order/OfflineOrderRepository.kt @@ -1,15 +1,31 @@ package com.example.flowershopapp.Entities.Repository.Order +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource +import com.example.flowershopapp.Database.AppContainer import com.example.flowershopapp.Entities.DAO.OrderDAO +import com.example.flowershopapp.Entities.Model.Bouquet import com.example.flowershopapp.Entities.Model.Order import com.example.flowershopapp.Entities.Model.OrdersWithBouquets import kotlinx.coroutines.flow.Flow class OfflineOrderRepository(private val orderDAO: OrderDAO) : OrderRepository { - override fun getAll(): List = orderDAO.getAll() + override fun getAll(): Flow> = Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + pagingSourceFactory = orderDAO::getAll + ).flow override fun getOrdersWithBouquet(): Flow> = orderDAO.getOrdersWithBouquet() override fun getOrderWithBouquet(id: Int): OrdersWithBouquets = orderDAO.getOrderWithBouquet(id) override suspend fun insert(order: Order) = orderDAO.insert(order) override suspend fun update(order: Order) = orderDAO.update(order) override suspend fun delete(order: Order) = orderDAO.delete(order) + fun getAllOrdersPagingSource(): PagingSource = orderDAO.getAll() + suspend fun deleteAll() = orderDAO.deleteAll() + suspend fun insertOrders(orders: List) = + orderDAO.insert(*orders.toTypedArray()) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/Order/OrderRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/Order/OrderRepository.kt index 2ed698a..bde2aa4 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/Order/OrderRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/Order/OrderRepository.kt @@ -1,5 +1,6 @@ package com.example.flowershopapp.Entities.Repository.Order +import androidx.paging.PagingData import androidx.room.Delete import androidx.room.Insert import androidx.room.Query @@ -9,7 +10,7 @@ import com.example.flowershopapp.Entities.Model.OrdersWithBouquets import kotlinx.coroutines.flow.Flow interface OrderRepository { - fun getAll(): List + fun getAll(): Flow> fun getOrdersWithBouquet(): Flow> fun getOrderWithBouquet(id: Int): OrdersWithBouquets suspend fun insert(order: Order) diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/OrderBouquets/OfflineOrdersWithBouquetsRepositoryRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/OrderBouquets/OfflineOrdersWithBouquetsRepositoryRepository.kt index bf7cb15..efde745 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/OrderBouquets/OfflineOrdersWithBouquetsRepositoryRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/OrderBouquets/OfflineOrdersWithBouquetsRepositoryRepository.kt @@ -2,9 +2,11 @@ package com.example.flowershopapp.Entities.Repository.OrderBouquets import com.example.flowershopapp.Entities.DAO.OrdersWithBouquet import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef +import com.example.flowershopapp.Entities.Model.OrdersWithBouquets +import kotlinx.coroutines.flow.Flow class OfflineOrdersWithBouquetsRepositoryRepository(private val orderBouquetsDAO: OrdersWithBouquet) : OrdersWithBouquetsRepository { + override suspend fun getAll() = orderBouquetsDAO.getAll() override suspend fun insert(order: OrderBouquetCrossRef) = orderBouquetsDAO.insert(order) - override suspend fun update(order: OrderBouquetCrossRef) = orderBouquetsDAO.update(order) override suspend fun delete(order: OrderBouquetCrossRef) = orderBouquetsDAO.delete(order) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/OrderBouquets/OrdersWithBouquetsRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/OrderBouquets/OrdersWithBouquetsRepository.kt index ab2417b..95dcad8 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/OrderBouquets/OrdersWithBouquetsRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/OrderBouquets/OrdersWithBouquetsRepository.kt @@ -1,9 +1,11 @@ package com.example.flowershopapp.Entities.Repository.OrderBouquets import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef +import com.example.flowershopapp.Entities.Model.OrdersWithBouquets +import kotlinx.coroutines.flow.Flow interface OrdersWithBouquetsRepository { + suspend fun getAll(): List suspend fun insert(order: OrderBouquetCrossRef) - suspend fun update(order: OrderBouquetCrossRef) suspend fun delete(order: OrderBouquetCrossRef) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/User/OfflineUserRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/User/OfflineUserRepository.kt index f8bf6fc..b1c098a 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/User/OfflineUserRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/User/OfflineUserRepository.kt @@ -1,15 +1,30 @@ package com.example.flowershopapp.Entities.Repository.User +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.flowershopapp.Database.AppContainer import com.example.flowershopapp.Entities.DAO.UserDAO +import com.example.flowershopapp.Entities.Model.Order import com.example.flowershopapp.Entities.Model.User import com.example.flowershopapp.Entities.Model.UsersWithOrders import kotlinx.coroutines.flow.Flow class OfflineUserRepository(private val userDAO: UserDAO) : UserRepository{ - override fun getAll(): List = userDAO.getAll() - override fun getUser(userName: String): User = userDAO.getUser(userName) - override fun getUserWithOrders(userName: String): Flow = userDAO.getUserWithOrders(userName) + override fun getAll(): Flow> = Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + pagingSourceFactory = userDAO::getAll + ).flow + override suspend fun getUser(userName: String): User = userDAO.getUser(userName) + override suspend fun getUserWithOrders(userName: String): Flow = userDAO.getUserWithOrders(userName) override fun getUsersWithOrders(): Flow> = userDAO.getUsersWithOrders() override suspend fun insert(user: User) = userDAO.insert(user) override suspend fun update(user: User) = userDAO.update(user) + override suspend fun delete(user: User) = userDAO.delete(user) + suspend fun deleteAll() = userDAO.deleteAll() + suspend fun insertUsers(users: List) = + userDAO.insert(*users.toTypedArray()) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/User/UserRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/User/UserRepository.kt index 785073b..291a499 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/User/UserRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/User/UserRepository.kt @@ -1,17 +1,20 @@ package com.example.flowershopapp.Entities.Repository.User +import androidx.paging.PagingData import androidx.room.Insert import androidx.room.Query import androidx.room.Update +import com.example.flowershopapp.Entities.Model.Order import com.example.flowershopapp.Entities.Model.User import com.example.flowershopapp.Entities.Model.UsersWithOrders import kotlinx.coroutines.flow.Flow interface UserRepository { - fun getAll(): List - fun getUser(userName: String): User - fun getUserWithOrders(userName: String): Flow + fun getAll(): Flow> + suspend fun getUser(userName: String): User + suspend fun getUserWithOrders(userName: String): Flow fun getUsersWithOrders(): Flow> suspend fun insert(user: User) suspend fun update(user: User) + suspend fun delete(user: User) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/UserOrders/OfflineUsersWithOrdersRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/UserOrders/OfflineUsersWithOrdersRepository.kt index fa7abd2..df78e77 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/UserOrders/OfflineUsersWithOrdersRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/UserOrders/OfflineUsersWithOrdersRepository.kt @@ -4,7 +4,7 @@ import com.example.flowershopapp.Entities.DAO.UsersWithOrders import com.example.flowershopapp.Entities.Model.UserOrderCrossRef class OfflineUsersWithOrdersRepository(private val userOrders: UsersWithOrders) : com.example.flowershopapp.Entities.Repository.UserOrders.UsersWithOrdersRepository { + override suspend fun getAll() = userOrders.getAll() override suspend fun insert(user: UserOrderCrossRef) = userOrders.insert(user) - override suspend fun update(user: UserOrderCrossRef) = userOrders.update(user) override suspend fun delete(user: UserOrderCrossRef) = userOrders.delete(user) } \ No newline at end of file diff --git a/app/src/main/java/com/example/flowershopapp/Entities/Repository/UserOrders/UsersWithOrdersRepository.kt b/app/src/main/java/com/example/flowershopapp/Entities/Repository/UserOrders/UsersWithOrdersRepository.kt index 44802d1..fe254e7 100644 --- a/app/src/main/java/com/example/flowershopapp/Entities/Repository/UserOrders/UsersWithOrdersRepository.kt +++ b/app/src/main/java/com/example/flowershopapp/Entities/Repository/UserOrders/UsersWithOrdersRepository.kt @@ -1,9 +1,10 @@ package com.example.flowershopapp.Entities.Repository.UserOrders +import com.example.flowershopapp.Entities.Model.OrderBouquetCrossRef import com.example.flowershopapp.Entities.Model.UserOrderCrossRef interface UsersWithOrdersRepository { + suspend fun getAll(): List suspend fun insert(user: UserOrderCrossRef) - suspend fun update(user: UserOrderCrossRef) suspend fun delete(user: UserOrderCrossRef) } \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..1b06270 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/server/data.json b/server/data.json new file mode 100644 index 0000000..24e2342 --- /dev/null +++ b/server/data.json @@ -0,0 +1,42 @@ +{ + "groups": [ + { + "id": 1, + "name": "ПИбд-21" + }, + { + "id": 2, + "name": "ПИбд-22" + }, + { + "name": "ПИбд-23", + "id": 3 + } + ], + "students": [ + { + "id": 2, + "firstName": "Test22244", + "lastName": "Test2", + "groupId": 2, + "phone": "Phone2", + "email": "Email2" + }, + { + "firstName": "Test3", + "lastName": "Test3", + "groupId": 1, + "phone": "Phone3", + "email": "Email3", + "id": 3 + }, + { + "firstName": "werwerwe", + "lastName": "werwer", + "groupId": 1, + "phone": "sefswer", + "email": "werwer", + "id": 4 + } + ] +} \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..0203f7f --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,1335 @@ +{ + "name": "fake-db", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fake-db", + "version": "1.0.0", + "devDependencies": { + "json-server": "0.17.4" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect-pause": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-pause/-/connect-pause-0.1.1.tgz", + "integrity": "sha512-a1gSWQBQD73krFXdUEYJom2RTFrWUL3YvXDCRkyv//GVXc79cdW9MngtRuN9ih4FDKBtfJAJId+BbDuX+1rh2w==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "dev": true, + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-urlrewrite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/express-urlrewrite/-/express-urlrewrite-1.4.0.tgz", + "integrity": "sha512-PI5h8JuzoweS26vFizwQl6UTF25CAHSggNv0J25Dn/IKZscJHWZzPrI5z2Y2jgOzIaw2qh8l6+/jUcig23Z2SA==", + "dev": true, + "dependencies": { + "debug": "*", + "path-to-regexp": "^1.0.3" + } + }, + "node_modules/express-urlrewrite/node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/express/node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true + }, + "node_modules/json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==", + "dev": true, + "dependencies": { + "jju": "^1.1.0" + } + }, + "node_modules/json-server": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/json-server/-/json-server-0.17.4.tgz", + "integrity": "sha512-bGBb0WtFuAKbgI7JV3A864irWnMZSvBYRJbohaOuatHwKSRFUfqtQlrYMrB6WbalXy/cJabyjlb7JkHli6dYjQ==", + "dev": true, + "dependencies": { + "body-parser": "^1.19.0", + "chalk": "^4.1.2", + "compression": "^1.7.4", + "connect-pause": "^0.1.1", + "cors": "^2.8.5", + "errorhandler": "^1.5.1", + "express": "^4.17.1", + "express-urlrewrite": "^1.4.0", + "json-parse-helpfulerror": "^1.0.3", + "lodash": "^4.17.21", + "lodash-id": "^0.14.1", + "lowdb": "^1.0.0", + "method-override": "^3.0.0", + "morgan": "^1.10.0", + "nanoid": "^3.1.23", + "please-upgrade-node": "^3.2.0", + "pluralize": "^8.0.0", + "server-destroy": "^1.0.1", + "yargs": "^17.0.1" + }, + "bin": { + "json-server": "lib/cli/bin.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-id": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/lodash-id/-/lodash-id-0.14.1.tgz", + "integrity": "sha512-ikQPBTiq/d5m6dfKQlFdIXFzvThPi2Be9/AHxktOnDSfSxE1j9ICbBT5Elk1ke7HSTgM38LHTpmJovo9/klnLg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/lowdb": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", + "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.3", + "is-promise": "^2.1.0", + "lodash": "4", + "pify": "^3.0.0", + "steno": "^0.4.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/method-override": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/method-override/-/method-override-3.0.0.tgz", + "integrity": "sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==", + "dev": true, + "dependencies": { + "debug": "3.1.0", + "methods": "~1.1.2", + "parseurl": "~1.3.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/method-override/node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dev": true, + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/steno": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz", + "integrity": "sha512-EEHMVYHNXFHfGtgjNITnka0aHhiAlo93F7z2/Pwd+g0teG9CnM3JIINM7hVVB5/rhw9voufD7Wukwgtw2uqh6w==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.3" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..0fb3100 --- /dev/null +++ b/server/package.json @@ -0,0 +1,12 @@ +{ + "name": "fake-db", + "version": "1.0.0", + "scripts": { + "start": "json-server --watch data.json --host 0.0.0.0 -p 8079" + }, + "dependencies": { + }, + "devDependencies": { + "json-server": "0.17.4" + } +} \ No newline at end of file diff --git a/server/readme.md b/server/readme.md new file mode 100644 index 0000000..89a58db --- /dev/null +++ b/server/readme.md @@ -0,0 +1,26 @@ +Установка nodejs: +1. Заходим на сайт https://nodejs.org/en/ +2. Скачиваем LTS версию +3. Устанавливаем + +Переход в каталог с сервером: +```commandline +cd ./server +``` + +Установка зависимостей: +```commandline +npm install +``` + +Запуск: +```commandline +npm start +``` + +Примеры: +- http://localhost:8079 +- http://localhost:8079/students +- http://localhost:8079/students?_expand=group + +Документация -- https://www.npmjs.com/package/json-server \ No newline at end of file