This commit is contained in:
shadowik 2023-12-19 22:54:15 +04:00
parent 8c81b83717
commit 118e8a0ea2
53 changed files with 1071 additions and 179 deletions

View File

@ -1,7 +1,10 @@
import org.apache.tools.ant.util.JavaEnvUtils.VERSION_11
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
@ -31,11 +34,11 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "17"
jvmTarget = "11"
}
buildFeatures {
compose = true
@ -49,9 +52,10 @@ android {
}
}
}
//kotlin {
//// jvmToolchain(11)
//}
kotlin {
jvmToolchain(17)
}
dependencies {
// Core
@ -75,6 +79,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")
@ -83,4 +95,6 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}

View File

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".FoodWarriorsApplication"
android:allowBackup="true"
@ -12,7 +14,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
android:exported="true"

View File

@ -1,10 +1,13 @@
package com.example.myapplication
import android.content.SharedPreferences
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.myapplication.database.PrefRepository
import com.example.myapplication.ui.dishes.list.DishListViewModel
import com.example.myapplication.ui.dishes.view.CategoryDropDownViewModel
import com.example.myapplication.ui.dishes.view.DishEditViewModel
@ -16,27 +19,28 @@ import com.example.myapplication.ui.user.UserViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
DishListViewModel(foodWarriorsApplication().container.dishRepository,
foodWarriorsApplication().container.userFavoritesRepository)
DishListViewModel(foodWarriorsApplication().container.dishRestRepository,
foodWarriorsApplication().container.userFavoritesRestRepository,
)
}
initializer {
ErrorsViewModel()
}
initializer {
UserViewModel(foodWarriorsApplication().container.userRepository)
}
initializer {
DishViewModel(foodWarriorsApplication().container.dishRepository)
}
initializer {
DishEditViewModel(
this.createSavedStateHandle(),
foodWarriorsApplication().container.dishRepository
)
}
initializer {
CategoryDropDownViewModel(foodWarriorsApplication().container.categoryRepository)
UserViewModel(foodWarriorsApplication().container.userRestRepository)
}
// initializer {
// DishViewModel(foodWarriorsApplication().container.dishRestRepository)
// }
// initializer {
// DishEditViewModel(
// this.createSavedStateHandle(),
// foodWarriorsApplication().container.dishRestRepository
// )
// }
// initializer {
// CategoryDropDownViewModel(foodWarriorsApplication().container.categoryRestRepository)
// }
}
}

View File

@ -1,6 +1,7 @@
package com.example.myapplication
import android.app.Application
import android.content.SharedPreferences
import com.example.myapplication.database.AppContainer
import com.example.myapplication.database.AppDataContainer

View File

@ -0,0 +1,6 @@
package com.example.myapplication.api
object ApiRoutes {
const val BASE = "http://10.0.2.2:8083/"
const val PREFIX = "api/"
}

View File

@ -0,0 +1,134 @@
package com.example.myapplication.api
import com.example.myapplication.api.model.CategoryRemote
import com.example.myapplication.api.model.DishRemote
import com.example.myapplication.api.model.UserFavoritesRemote
import com.example.myapplication.api.model.UserRemote
import com.example.myapplication.database.model.Dish
import com.example.myapplication.database.model.User
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.coroutines.flow.Flow
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 ServerService {
@POST(ApiRoutes.PREFIX + "users")
suspend fun insertUser(
@Body user: UserRemote
)
@GET(ApiRoutes.PREFIX + "users")
suspend fun getAllUsers() : List<UserRemote>
@GET(ApiRoutes.PREFIX + "user/{id}")
suspend fun getUserById(
@Path("id") id: Int
): UserRemote?
@PUT(ApiRoutes.PREFIX + "user/{id}")
suspend fun updateUser(
@Path("id") id: Int,
@Body user: UserRemote
)
@DELETE(ApiRoutes.PREFIX + "user/{id}")
suspend fun deleteUser(
@Path("id") id: Int,
)
@GET(ApiRoutes.PREFIX + "categories")
suspend fun getCategories() : List<CategoryRemote>
@POST(ApiRoutes.PREFIX + "categories")
suspend fun insertCategory(
@Body category: CategoryRemote
)
@POST(ApiRoutes.PREFIX + "dishes")
suspend fun insertDish(
@Body dish: DishRemote
)
@GET(ApiRoutes.PREFIX + "dishes")
suspend fun getAllDishes(): List<DishRemote>
@GET(ApiRoutes.PREFIX + "user_dish/{id}")
suspend fun getAllUserDishes(
@Path("id") userId: Int
): List<DishRemote>
@GET(ApiRoutes.PREFIX + "dish/{id}")
suspend fun getDish(
@Path("id") dishId: Int
): DishRemote
@PUT(ApiRoutes.PREFIX + "dish/{id}")
suspend fun editDish(
@Path("id") dishId: Int,
@Body dish: DishRemote
)
@DELETE(ApiRoutes.PREFIX + "dish/{id}")
suspend fun deleteDish(
@Path("id") dishId: Int,
)
@GET(ApiRoutes.PREFIX + "favorites/{id}")
suspend fun getFavoritesOfUser(
@Path("id") userId: Int
): List<DishRemote>
@DELETE(ApiRoutes.PREFIX + "favorite/{userId}/{dishId}")
suspend fun deleteFavorite(
@Path("userId") userId: Int,
@Path("dishId") dishId: Int,
)
@POST(ApiRoutes.PREFIX + "favorite/{userId}/{dishId}")
suspend fun createFavorite(
@Path("userId") userId: Int,
@Path("dishId") dishId: Int,
)
@DELETE(ApiRoutes.PREFIX + "favorite/{userId}/{dishId}")
suspend fun getUserFavorite(
@Path("userId") userId: Int,
@Path("dishId") dishId: Int,
) : UserFavoritesRemote
companion object {
private const val BASE_URL = ApiRoutes.BASE
@Volatile
private var INSTANCE: ServerService? = null
fun getInstance(): ServerService {
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(ServerService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,20 @@
package com.example.myapplication.api.model
import com.example.myapplication.database.model.Category
import kotlinx.serialization.Serializable
@Serializable
data class CategoryRemote(
val uid: Int = 0,
val name: String = "",
)
fun CategoryRemote.toCategory(): Category = Category(
uid = uid,
name = name
)
fun Category.toCategoryRemote(): CategoryRemote = CategoryRemote(
uid = uid!!,
name = name
)

View File

@ -0,0 +1,35 @@
package com.example.myapplication.api.model
import com.example.myapplication.database.model.Category
import com.example.myapplication.database.model.Dish
import com.example.myapplication.database.model.DishWithCategoryAndUser
import kotlinx.serialization.Serializable
@Serializable
data class DishRemote(
val id: Int = 0,
val name: String = "",
val description: String = "",
val image: String?,
val userId: Int = 0,
val categoryId: Int = 0,
)
fun DishRemote.toDish(): Dish = Dish(
uid = id,
name = name,
description = description,
image = ByteArray(1),
userId = userId,
categoryId = categoryId
)
fun Dish.toDishRemote(): DishRemote = DishRemote(
id = uid!!,
name = name,
description = description,
image = image.toString(),
userId = userId!!,
categoryId = categoryId!!
)

View File

@ -0,0 +1,21 @@
package com.example.myapplication.api.model
import com.example.myapplication.database.model.UserFavorites
import kotlinx.serialization.Serializable
@Serializable
data class UserFavoritesRemote(
val userId: Int = 0,
val dishId: Int = 0,
)
fun UserFavoritesRemote.toUserFavorites(): UserFavorites = UserFavorites(
userId = userId,
dishId = dishId
)
fun UserFavorites.toRemote(): UserFavoritesRemote = UserFavoritesRemote(
userId = userId,
dishId = dishId
)

View File

@ -0,0 +1,27 @@
package com.example.myapplication.api.model
import com.example.myapplication.database.model.User
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
val id: Int = 0,
val nickname: String = "",
val email: String = "",
val hashed_password: String = ""
)
fun UserRemote.toUser(): User = User(
uid = id,
nickname = nickname,
email = email,
password = hashed_password
)
fun User.toRemote(): UserRemote = UserRemote(
id = uid!!,
nickname = nickname,
email = email,
hashed_password = password
)

View File

@ -0,0 +1,26 @@
package com.example.myapplication.api.repository
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.model.toCategory
import com.example.myapplication.api.model.toCategoryRemote
import com.example.myapplication.database.dao.CategoryDao
import com.example.myapplication.database.model.Category
import com.example.myapplication.database.repository.CategoryRepository
import com.example.myapplication.database.repository.OfflineCategoryRepository
import kotlinx.coroutines.flow.Flow
class RestCategoryRepository(
private val service: ServerService,
private val dbCategoryRepository: OfflineCategoryRepository,
) : CategoryRepository {
override suspend fun getAll(): List<Category> {
dbCategoryRepository.deleteAll()
val categories = service.getCategories().map { it.toCategory() }
categories.forEach() {dbCategoryRepository.insert(it)}
return categories
}
override suspend fun insert(category: Category) {
service.insertCategory(category.toCategoryRemote())
}
}

View File

@ -0,0 +1,43 @@
package com.example.myapplication.api.repository
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.model.toDish
import com.example.myapplication.api.model.toDishRemote
import com.example.myapplication.database.dao.DishDao
import com.example.myapplication.database.model.Dish
import com.example.myapplication.database.model.DishWithCategoryAndUser
import com.example.myapplication.database.repository.DishRepository
import com.example.myapplication.database.repository.OfflineCategoryRepository
import com.example.myapplication.database.repository.OfflineDishRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
class RestDishRepository(private val service: ServerService,
private val dbCategoryRepository: OfflineDishRepository
) : DishRepository {
override suspend fun getAllDishes(): List<Dish> {
return service.getAllDishes().map { it.toDish() }
}
override suspend fun getDish(uid: Int): Dish? {
return service.getDish(uid).toDish()
}
override suspend fun getUserDishes(userUid: Int): List<Dish> {
return service.getAllUserDishes(userUid).map { it.toDish() }
}
override suspend fun insertDish(dish: Dish) {
service.insertDish(dish.toDishRemote())
}
override suspend fun updateDish(dish: Dish) {
service.editDish(dish.uid!!, dish.toDishRemote())
}
override suspend fun deleteDish(dish: Dish) {
service.deleteDish(dish.uid!!)
}
}

View File

@ -0,0 +1,32 @@
package com.example.myapplication.api.repository
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.model.toDish
import com.example.myapplication.api.model.toUserFavorites
import com.example.myapplication.database.model.Dish
import com.example.myapplication.database.model.UserFavorites
import com.example.myapplication.database.repository.OfflineUserFavoritesRepository
import com.example.myapplication.database.repository.UserWithFavoritesRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class RestUserFavoritesRepository(
private val service: ServerService,
private val dbCategoryRepository: OfflineUserFavoritesRepository,
) : UserWithFavoritesRepository {
override suspend fun getUserFavorites(userUid: Int): List<Dish> {
return service.getFavoritesOfUser(userUid).map { it.toDish() }
}
override suspend fun getUserFavorite(userUid: Int, dishUid: Int): UserFavorites? {
return service.getUserFavorite(userUid, dishUid).toUserFavorites()
}
override suspend fun deleteUserFavorites(userUid: Int, dishUid: Int) {
service.deleteFavorite(userUid, dishUid)
}
override suspend fun insertUserFavorites(userUid: Int, dishUid: Int) {
service.createFavorite(userUid, dishUid)
}
}

View File

@ -0,0 +1,42 @@
package com.example.myapplication.api.repository
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.model.toRemote
import com.example.myapplication.api.model.toUser
import com.example.myapplication.api.model.toUserFavorites
import com.example.myapplication.database.model.User
import com.example.myapplication.database.repository.OfflineUserRepository
import com.example.myapplication.database.repository.UserRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import retrofit2.HttpException
class RestUserRepository(
private val service: ServerService,
private val dbCategoryRepository: OfflineUserRepository
) : UserRepository {
override suspend fun getAllUsers(): List<User> {
return service.getAllUsers().map { it.toUser() }
}
override suspend fun getUser(uid: Int): User? {
try {
return service.getUserById(uid)!!.toUser()
}
catch (e: HttpException) {
return null
}
}
override suspend fun insertUser(user: User) {
service.insertUser(user.toRemote())
}
override suspend fun updateUser(user: User) {
service.updateUser(user.uid!!, user.toRemote())
}
override suspend fun deleteUser(user: User) {
service.deleteUser(user.uid!!)
}
}

View File

@ -1,6 +1,11 @@
package com.example.myapplication.database
import android.content.Context
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.repository.RestCategoryRepository
import com.example.myapplication.api.repository.RestDishRepository
import com.example.myapplication.api.repository.RestUserFavoritesRepository
import com.example.myapplication.api.repository.RestUserRepository
import com.example.myapplication.database.repository.CategoryRepository
import com.example.myapplication.database.repository.DishRepository
import com.example.myapplication.database.repository.OfflineCategoryRepository
@ -12,30 +17,60 @@ import com.example.myapplication.database.repository.UserWithFavoritesRepository
interface AppContainer {
val userRepository : UserRepository
val dishRepository : DishRepository
val categoryRepository : CategoryRepository
val userFavoritesRepository: UserWithFavoritesRepository
val userRestRepository : UserRepository
val dishRestRepository : DishRepository
val categoryRestRepository : CategoryRepository
val userFavoritesRestRepository: UserWithFavoritesRepository
}
class AppDataContainer(private val context: Context) : AppContainer {
override val userRepository: UserRepository by lazy {
val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao())
}
override val dishRepository: DishRepository by lazy {
val dishRepository: OfflineDishRepository by lazy {
OfflineDishRepository(AppDatabase.getInstance(context).dishDao())
}
override val categoryRepository: CategoryRepository by lazy {
val categoryRepository: OfflineCategoryRepository by lazy {
OfflineCategoryRepository(AppDatabase.getInstance(context).categoryDao())
}
override val userFavoritesRepository: UserWithFavoritesRepository by lazy {
val userFavoritesRepository: OfflineUserFavoritesRepository by lazy {
OfflineUserFavoritesRepository(AppDatabase.getInstance(context).userFavoritesDao())
}
override val userFavoritesRestRepository: UserWithFavoritesRepository by lazy {
RestUserFavoritesRepository(
service = ServerService.getInstance(),
dbCategoryRepository = userFavoritesRepository
)
}
override val userRestRepository: UserRepository by lazy {
RestUserRepository(
service = ServerService.getInstance(),
dbCategoryRepository = userRepository
)
}
override val dishRestRepository: DishRepository by lazy {
RestDishRepository(
service = ServerService.getInstance(),
dbCategoryRepository = dishRepository
)
}
override val categoryRestRepository: CategoryRepository by lazy {
RestCategoryRepository(
service = ServerService.getInstance(),
dbCategoryRepository = categoryRepository
)
}
companion object {
const val TIMEOUT = 5000L
}

View File

@ -1,24 +1,35 @@
package com.example.myapplication.database
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.myapplication.database.model.Category
import com.example.myapplication.database.model.Dish
import com.example.myapplication.database.dao.CategoryDao
import com.example.myapplication.database.dao.DishDao
import com.example.myapplication.database.model.User
import com.example.myapplication.database.model.UserFavorites
import com.example.myapplication.database.dao.UserDao
import com.example.myapplication.database.dao.UserFavoritesDao
import com.example.myapplication.database.model.Category
import com.example.myapplication.database.model.Dish
import com.example.myapplication.database.model.User
import com.example.myapplication.database.model.UserFavorites
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Database(entities = [Dish::class, User::class, Category::class, UserFavorites::class],
version = 1, exportSchema = false)
@Database(
entities = [
Dish::class,
User::class,
Category::class,
UserFavorites::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun dishDao() : DishDao
abstract fun userDao() : UserDao

View File

@ -0,0 +1,15 @@
package com.example.myapplication.database
import android.content.Context
import android.content.SharedPreferences
class PrefRepository(context: Context) {
private val pref: SharedPreferences = context.getSharedPreferences("FoodWarPref", Context.MODE_PRIVATE)
val editor = pref.edit()
fun loadId(id: Int) {
editor.putInt("user_id", id)
editor.commit()
}
fun getId() = pref.getInt("user_id", 2)
}

View File

@ -21,4 +21,7 @@ interface CategoryDao {
@Delete
suspend fun delete(category: Category)
@Query("DELETE FROM categories")
suspend fun deleteAll()
}

View File

@ -12,14 +12,14 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface DishDao {
@Query("select * from dishes order by dish_name collate nocase asc")
fun getAll(): Flow<List<Dish>>
suspend fun getAll(): List<Dish>
@Query("select * from dishes left join categories on dishes.category_id = categories.category_id " +
"left join users on dishes.user_id = users.user_id where dishes.dish_id = :uid")
fun getByUid(uid: Int): Flow<DishWithCategoryAndUser?>
suspend fun getByUid(uid: Int): Dish?
@Query("select * from dishes where dishes.user_id = :uid")
fun getAllOFUser(uid: Int): Flow<List<Dish>>
suspend fun getAllOFUser(uid: Int): List<Dish>
@Insert
suspend fun insert(category: Dish)

View File

@ -12,10 +12,10 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("select * from users")
fun getAll(): Flow<List<User>>
suspend fun getAll(): List<User>
@Query("select * from users where users.user_id = :uid")
fun getByUid(uid: Int): Flow<User?>
suspend fun getByUid(uid: Int): User?
@Query("select * from user_favorites left join users on user_favorites.user_id = users.user_id " +

View File

@ -24,13 +24,13 @@ interface UserFavoritesDao {
@Transaction
@Query("SELECT * FROM dishes")
fun getUserWithFavorites(): Flow<List<Dish>>
suspend fun getUserWithFavorites(): List<Dish>
@Transaction
@Query("SELECT * FROM user_favorites us JOIN dishes d ON d.dish_id = us.dish_id WHERE us.user_id = :uid")
fun getUserFavorites(uid: Int?): Flow<List<Dish>>
suspend fun getUserFavorites(uid: Int?): List<Dish>
@Transaction
@Query("SELECT * FROM user_favorites us WHERE us.user_id = :userUid AND us.dish_id = :dishUid")
fun getUserFavorite(userUid: Int?, dishUid: Int?): Flow<UserFavorites?>
suspend fun getUserFavorite(userUid: Int?, dishUid: Int?): UserFavorites?
}

View File

@ -1,8 +1,8 @@
package com.example.myapplication.database.repository
import com.example.myapplication.database.model.Category
import kotlinx.coroutines.flow.Flow
interface CategoryRepository {
suspend fun getAllCategories(): List<Category>
suspend fun getAll(): List<Category>
suspend fun insert(category: Category)
}

View File

@ -5,10 +5,9 @@ import com.example.myapplication.database.model.DishWithCategoryAndUser
import kotlinx.coroutines.flow.Flow
interface DishRepository {
fun getAllDishes(): Flow<List<Dish>>
fun getDish(uid: Int): Flow<DishWithCategoryAndUser?>
fun getUserDishes(userUid: Int) : Flow<List<Dish>>
suspend fun getAllDishes(): List<Dish>
suspend fun getDish(uid: Int): Dish?
suspend fun getUserDishes(userUid: Int) : List<Dish>
suspend fun insertDish(dish: Dish)
suspend fun updateDish(dish: Dish)
suspend fun deleteDish(dish: Dish)

View File

@ -5,7 +5,12 @@ import com.example.myapplication.database.model.Category
import kotlinx.coroutines.flow.Flow
class OfflineCategoryRepository(private val categoryDao: CategoryDao) : CategoryRepository {
override suspend fun getAllCategories(): List<Category> {
override suspend fun getAll(): List<Category> {
return categoryDao.getAll();
}
override suspend fun insert(category: Category) = categoryDao.insert(category)
suspend fun deleteAll() = categoryDao.deleteAll()
}

View File

@ -6,15 +6,15 @@ import com.example.myapplication.database.model.DishWithCategoryAndUser
import kotlinx.coroutines.flow.Flow
class OfflineDishRepository(private val dishDao: DishDao) : DishRepository {
override fun getAllDishes(): Flow<List<Dish>> {
override suspend fun getAllDishes(): List<Dish> {
return dishDao.getAll()
}
override fun getDish(uid: Int): Flow<DishWithCategoryAndUser?> {
override suspend fun getDish(uid: Int): Dish? {
return dishDao.getByUid(uid)
}
override fun getUserDishes(userUid: Int): Flow<List<Dish>> {
override suspend fun getUserDishes(userUid: Int): List<Dish> {
return dishDao.getAllOFUser(userUid);
}

View File

@ -6,14 +6,15 @@ import com.example.myapplication.database.model.UserFavorites
import kotlinx.coroutines.flow.Flow
class OfflineUserFavoritesRepository(private val userFavoritesDao: UserFavoritesDao) : UserWithFavoritesRepository {
override fun getUserFavorites(userUid: Int): Flow<List<Dish>> {
override suspend fun getUserFavorites(userUid: Int): List<Dish> {
return userFavoritesDao.getUserFavorites(userUid)
}
override fun getUserFavorite(userUid: Int, dishUid: Int): Flow<UserFavorites?> {
override suspend fun getUserFavorite(userUid: Int, dishUid: Int): UserFavorites? {
return userFavoritesDao.getUserFavorite(userUid, dishUid)
}
override suspend fun deleteUserFavorites(userUid: Int, dishUid: Int) {
userFavoritesDao.delete(UserFavorites(userUid, dishUid))
}

View File

@ -5,11 +5,11 @@ import com.example.myapplication.database.model.User
import kotlinx.coroutines.flow.Flow
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override fun getAllUsers(): Flow<List<User>> {
override suspend fun getAllUsers(): List<User> {
return userDao.getAll()
}
override fun getUser(uid: Int): Flow<User?> {
override suspend fun getUser(uid: Int): User? {
return userDao.getByUid(uid)
}

View File

@ -4,8 +4,8 @@ import com.example.myapplication.database.model.User
import kotlinx.coroutines.flow.Flow
interface UserRepository {
fun getAllUsers(): Flow<List<User>>
fun getUser(uid: Int): Flow<User?>
suspend fun getAllUsers(): List<User>
suspend fun getUser(uid: Int): User?
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)

View File

@ -5,9 +5,9 @@ import com.example.myapplication.database.model.UserFavorites
import kotlinx.coroutines.flow.Flow
interface UserWithFavoritesRepository {
fun getUserFavorites(userUid: Int): Flow<List<Dish>>
suspend fun getUserFavorites(userUid: Int): List<Dish>
fun getUserFavorite(userUid: Int, dishUid: Int): Flow<UserFavorites?>
suspend fun getUserFavorite(userUid: Int, dishUid: Int): UserFavorites?
suspend fun deleteUserFavorites(userUid: Int, dishUid: Int)

View File

@ -1,5 +1,6 @@
package com.example.myapplication.ui.dishes.list
import android.content.SharedPreferences
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
@ -19,20 +20,14 @@ import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@ -44,17 +39,12 @@ import androidx.navigation.NavController
import com.example.myapplication.AppViewModelProvider
import com.example.myapplication.database.model.Dish
import com.example.myapplication.R
import com.example.myapplication.database.model.UserFavorites
import com.example.myapplication.database.AppDatabase
import com.example.myapplication.database.model.User
import com.example.myapplication.database.PrefRepository
import com.example.myapplication.ui.extra.ErrorElement
import com.example.myapplication.ui.extra.ErrorsType
import com.example.myapplication.ui.navigation.Screen
import com.example.myapplication.ui.theme.MyApplicationTheme
import com.example.myapplication.ui.theme.textFont
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(Build.VERSION_CODES.Q)
@ -67,8 +57,10 @@ fun DishList(
) {
val coroutineScope = rememberCoroutineScope()
val dishListUiState by viewModel.dishListUiState.collectAsState()
val userDishes by viewModel.userDishes(userUid ?: 0).collectAsState()
val favorites by viewModel.dishFavorites(userUid ?: 0).collectAsState()
val dishFavoritesUiState by viewModel.favoritesDishListUiState.collectAsState()
val dishUserUiState by viewModel.userDishListUiState.collectAsState()
Scaffold(
) { innerPadding ->
if (typeDishList != TypeDishList.AllDishes && userUid == null) {
@ -81,13 +73,13 @@ fun DishList(
.fillMaxSize()
.verticalScroll(ScrollState(0)),
typeDishList = typeDishList,
dishList = when (typeDishList) {
dishListTytle = when (typeDishList) {
TypeDishList.AllDishes -> dishListUiState.dishList
TypeDishList.FavoritesDishes -> favorites.dishList
TypeDishList.UserDishes -> userDishes.dishList
TypeDishList.FavoritesDishes -> dishFavoritesUiState.dishList
TypeDishList.UserDishes -> dishUserUiState.dishList
else -> dishListUiState.dishList
},
favorites = favorites.dishList,
favorites = dishFavoritesUiState.dishList,
addToFavorites = {uid: Int ->
if (userUid != null) {
coroutineScope.launch {
@ -109,7 +101,7 @@ fun DishList(
private fun DishList(
modifier: Modifier = Modifier,
typeDishList: TypeDishList,
dishList: List<Dish>,
dishListTytle: List<Dish>,
favorites: List<Dish>,
addToFavorites: (uid : Int) -> Unit,
onClick: (uid : Int) -> Unit
@ -130,7 +122,7 @@ private fun DishList(
fontSize=26.sp, textAlign = TextAlign.Start)
}
}
dishList.forEachIndexed() {index, dish ->
dishListTytle.forEachIndexed() { index, dish ->
DishListItem(index=index, dish = dish, favorites = favorites, addToFavorites, onClick = onClick)
}
}
@ -154,7 +146,8 @@ private fun DishListItem(
}) {
Column() {
if (dish.image != null) { // TODO Image input check
// if (dish.image != null) { // TODO Image input check
if (false) {
Image(
bitmap = dish.getBitmapFromByteArray()!!.asImageBitmap(),
contentDescription = "Dish Image",

View File

@ -1,16 +1,24 @@
package com.example.myapplication.ui.dishes.list
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.database.AppDataContainer
import com.example.myapplication.database.PrefRepository
import com.example.myapplication.database.model.Dish
import com.example.myapplication.database.repository.DishRepository
import com.example.myapplication.database.repository.UserWithFavoritesRepository
import com.example.myapplication.ui.user.UserUiState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
enum class TypeDishList {
AllDishes,
@ -19,48 +27,45 @@ enum class TypeDishList {
}
class DishListViewModel(
private val dishRepository: DishRepository,
private val userWithFavoritesRepository: UserWithFavoritesRepository
private val userWithFavoritesRepository: UserWithFavoritesRepository,
) : ViewModel() {
val dishListUiState: StateFlow<DishListUiState> = dishRepository.getAllDishes().map {
DishListUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = DishListUiState()
)
fun dishFavorites(userUid: Int) : StateFlow<DishListUiState> {
return userWithFavoritesRepository.getUserFavorites(userUid).map {
DishListUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = DishListUiState()
)
}
private val _dishListUiState = MutableStateFlow(DishListUiState())
val dishListUiState: StateFlow<DishListUiState> = _dishListUiState.asStateFlow()
fun userDishes(userUid: Int) : StateFlow<DishListUiState> {
return dishRepository.getUserDishes(userUid).map {
DishListUiState(it)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = DishListUiState()
)
}
private val _userDishListUiState = MutableStateFlow(DishListUiState())
val userDishListUiState: StateFlow<DishListUiState> = _dishListUiState.asStateFlow()
suspend fun addFavoritesToUser(userUid: Int, dishUid: Int) {
var flag = true;
userWithFavoritesRepository.getUserFavorite(userUid, dishUid).collect {
if (flag) {
if (it == null) {
flag = false
userWithFavoritesRepository.insertUserFavorites(userUid, dishUid)
} else {
flag = false
userWithFavoritesRepository.deleteUserFavorites(userUid, dishUid)
}
private val _favoritesDishListUiState = MutableStateFlow(DishListUiState())
val favoritesDishListUiState: StateFlow<DishListUiState> = _dishListUiState.asStateFlow()
init {
viewModelScope.launch {
_dishListUiState.update { currentScope ->
currentScope.copy(dishRepository.getAllDishes())
}
_favoritesDishListUiState.update { currentScope ->
currentScope.copy(userWithFavoritesRepository.getUserFavorites(2))
}
_userDishListUiState.update { currentScope ->
currentScope.copy(dishRepository.getUserDishes(2))
}
}
}
suspend fun addFavoritesToUser(userId: Int, dishId: Int) {
val userFavorite = userWithFavoritesRepository.getUserFavorite(userId, dishId)
if (userFavorite == null) {
userWithFavoritesRepository.insertUserFavorites(userId, dishId)
}
else {
userWithFavoritesRepository.deleteUserFavorites(userId, dishId)
}
_favoritesDishListUiState.update { currentScope ->
currentScope.copy(userWithFavoritesRepository.getUserFavorites(userId))
}
}
}

View File

@ -20,7 +20,7 @@ class CategoryDropDownViewModel(
init {
viewModelScope.launch {
categoryListUiState = CategoryListUiState(categoryRepository.getAllCategories())
categoryListUiState = CategoryListUiState(categoryRepository.getAll())
}
}

View File

@ -23,16 +23,16 @@ class DishEditViewModel (
private val dishUid: Int = checkNotNull(savedStateHandle["id"])
init {
viewModelScope.launch {
if (dishUid > 0) {
dishEditUiState = dishRepository.getDish(dishUid)
.filterNotNull()
.first().dish!!
.toUiState(true)
}
}
}
// init {
// viewModelScope.launch {
// if (dishUid > 0) {
// dishEditUiState = dishRepository.getDish(dishUid)
// .filterNotNull()
// .first().dish!!
// .toUiState(true)
// }
// }
// }
fun updateUiState(dishDetails: DishDetails) {
dishEditUiState = DishEditUiState(
dishDetails = dishDetails,

View File

@ -62,33 +62,33 @@ fun DishView(navController: NavController,
userUid: Int?,
dishUid: Int?) {
val coroutineScope = rememberCoroutineScope()
val dishUiState by viewModel.getDish(dishUid ?: 0).collectAsState()
Scaffold(
floatingActionButton = {
if (dishUiState.dish?.dish?.userId == userUid) {
FloatingActionButton(
onClick = {
val route = Screen.DishEdit.route.replace("{id}", dishUid.toString())
navController.navigate(route)
},
) {
Icon(Icons.Filled.Add, "Добавить")
}
}
}
){ innerPadding ->
if (dishUiState.dish == null) {
ErrorElement(navController = navController, typeErrorsType = ErrorsType.NOT_FOUND)
}
else {
DishView(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
dish = dishUiState.dish!!
)
}
}
// val dishUiState by viewModel.getDish(dishUid ?: 0).collectAsState()
// Scaffold(
// floatingActionButton = {
// if (dishUiState.dish?.dish?.userId == userUid) {
// FloatingActionButton(
// onClick = {
// val route = Screen.DishEdit.route.replace("{id}", dishUid.toString())
// navController.navigate(route)
// },
// ) {
// Icon(Icons.Filled.Add, "Добавить")
// }
// }
// }
// ){ innerPadding ->
// if (dishUiState.dish == null) {
// ErrorElement(navController = navController, typeErrorsType = ErrorsType.NOT_FOUND)
// }
// else {
// DishView(
// modifier = Modifier
// .padding(innerPadding)
// .fillMaxSize(),
// dish = dishUiState.dish!!
// )
// }
// }
}
@RequiresApi(Build.VERSION_CODES.Q)

View File

@ -13,15 +13,15 @@ import kotlinx.coroutines.flow.stateIn
class DishViewModel(
private val dishRepository: DishRepository
) : ViewModel() {
fun getDish(uid: Int) : StateFlow<DishViewUiState> {
return dishRepository.getDish(uid).map {
DishViewUiState(it!!)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = DishViewUiState()
)
}
// fun getDish(uid: Int) : StateFlow<DishViewUiState> {
// return dishRepository.getDish(uid).map {
// DishViewUiState(it!!)
// }.stateIn(
// scope = viewModelScope,
// started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
// initialValue = DishViewUiState()
// )
// }
}
data class DishViewUiState(val dish: DishWithCategoryAndUser? = null)

View File

@ -40,7 +40,7 @@ fun Navhost(
innerPadding: PaddingValues, modifier:
Modifier = Modifier
) {
val userUid = 1;
val userUid = 2;
NavHost(
navController,
startDestination = Screen.AllDishes.route,

View File

@ -20,6 +20,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -35,6 +36,8 @@ import com.example.myapplication.ui.extra.ErrorsType
import com.example.myapplication.ui.navigation.Screen
//import com.example.myapplication.User.Model.getAllUsers
import com.example.myapplication.ui.theme.textFont
import kotlinx.coroutines.launch
import java.util.prefs.Preferences
@RequiresApi(Build.VERSION_CODES.Q)
@OptIn(ExperimentalMaterial3Api::class)
@ -45,7 +48,11 @@ fun UserView (
userUid: Int?
) {
val coroutineScope = rememberCoroutineScope()
val userUiState by viewModel.getUser(userUid ?: 0).collectAsState()
val userUiState by viewModel.userState.collectAsState()
coroutineScope.let {
it.launch { viewModel.getUser(userUid ?: 0) }
}
Scaffold(
floatingActionButton = {
@ -66,8 +73,8 @@ fun UserView (
UserView(
navController = navController,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
.padding(innerPadding)
.fillMaxSize(),
user = userUiState.user!!
)
}

View File

@ -9,22 +9,24 @@ import com.example.myapplication.database.repository.DishRepository
import com.example.myapplication.database.repository.UserRepository
import com.example.myapplication.database.repository.UserWithFavoritesRepository
import com.example.myapplication.ui.dishes.list.DishListUiState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
class UserViewModel(
private val userRepository: UserRepository,
) : ViewModel() {
fun getUser(uid: Int) : StateFlow<UserUiState> {
return userRepository.getUser(uid).map {
UserUiState(it!!)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT),
initialValue = UserUiState()
)
private val _userState = MutableStateFlow(UserUiState())
val userState: StateFlow<UserUiState> = _userState.asStateFlow()
suspend fun getUser(uid: Int) {
_userState.update { currentState ->
currentState.copy(userRepository.getUser(uid))
}
}
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>

View File

@ -3,4 +3,5 @@ plugins {
id("com.android.application") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.10" apply false
}

34
server/app.py Normal file
View File

@ -0,0 +1,34 @@
from flask import Flask, url_for, render_template, jsonify, make_response, abort, redirect, request
from flask_login import LoginManager, login_user, login_required, logout_user, current_user
from flask_restful import Api
from recources import db_session, UserFavoritesResource
from recources.CategoryResource import CategoryListResource, CategoryResource
from recources.DishResource import DishListResource, DishResource, DishUserListResource
from recources.UserFavoritesResource import UsersFavoritesListResource, UserFavoriteResource
from recources.UserResource import UsersListResource, UserResource
app = Flask(__name__)
app.config['SECRET_KEY'] = 'my_project_key'
db_session.global_init('dishWarriors.db')
api = Api(app)
api.prefix = "/api"
api.add_resource(UsersListResource, '/users')
api.add_resource(UserResource, "/user/<int:user_id>")
api.add_resource(CategoryListResource, '/categories')
api.add_resource(CategoryResource, "/category/<int:category_id>")
api.add_resource(DishListResource, '/dishes')
api.add_resource(DishResource, "/dish/<int:dish_id>")
api.add_resource(DishUserListResource, "/user_dish/<int:user_id>")
api.add_resource(UserFavoriteResource, "/favorite/<int:user_id>/<int:dish_id>")
api.add_resource(UsersFavoritesListResource, "/favorites/<int:user_id>")
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'})), 404
if __name__ == '__main__':
app.run(port=8083, host='127.0.0.1')

BIN
server/dishWarriors.db Normal file

Binary file not shown.

View File

@ -0,0 +1,64 @@
from flask_restful import reqparse, abort, Resource, marshal_with, fields
from . import db_session
from recources.Model.Category import Category
from flask import jsonify
def abort_if_user_not_found(category_id):
db_sess = db_session.create_session()
user = db_sess.query(Category).get(category_id)
if not user:
abort(404, message=f'Category {category_id} not found')
parser = reqparse.RequestParser()
parser.add_argument('name', required=True)
class CategoryResource(Resource):
def get(self, category_id):
abort_if_user_not_found(category_id)
db_sess = db_session.create_session()
category = db_sess.query(Category).get(category_id)
return jsonify(
{'category': category.to_dict()}
)
def delete(self, category_id):
abort_if_user_not_found(category_id)
db_sess = db_session.create_session()
category = db_sess.query(Category).get(category_id)
db_sess.delete(category)
db_sess.commit()
return jsonify({'success': 'OK'})
def put(self, category_id):
abort_if_user_not_found(category_id)
args = parser.parse_args()
db_sess = db_session.create_session()
category = db_sess.query(Category).get(category_id)
category.name = args["name"]
db_sess.commit()
return jsonify({'success': 'OK'})
class CategoryListResource(Resource):
def get(self):
session = db_session.create_session()
categories = session.query(Category).all()
return jsonify([item.to_dict() for item in categories])
def post(self):
args = parser.parse_args()
db_sess = db_session.create_session()
category = Category()
category.name = args["name"]
db_sess.add(category)
db_sess.commit()
return jsonify({'success': 'OK'})

View File

@ -0,0 +1,82 @@
from flask_restful import reqparse, abort, Resource
from . import db_session
from recources.Model.Dish import Dish
from flask import jsonify
def abort_if_dish_not_found(dish_id):
db_sess = db_session.create_session()
user = db_sess.query(Dish).get(dish_id)
if not user:
abort(404, message=f'Dish {dish_id} not found')
parser = reqparse.RequestParser()
parser.add_argument('name', required=True)
parser.add_argument("description", required=True)
parser.add_argument("image", required=True)
parser.add_argument("userId", required=True)
parser.add_argument("categoryId", required=True)
class DishResource(Resource):
def get(self, dish_id):
abort_if_dish_not_found(dish_id)
db_sess = db_session.create_session()
dish = db_sess.query(Dish)
return jsonify(dish.to_dict())
def delete(self, dish_id):
abort_if_dish_not_found(dish_id)
db_sess = db_session.create_session()
dish = db_sess.query(Dish).get(dish_id)
db_sess.delete(dish)
db_sess.commit()
return jsonify({'success': 'OK'})
def put(self, dish_id):
abort_if_dish_not_found(dish_id)
args = parser.parse_args()
db_sess = db_session.create_session()
dish = db_sess.query(Dish).get(dish_id)
dish.name = args["name"]
dish.description = args["description"]
dish.image = args["image"]
dish.userId = args["userId"]
dish.categoryId = args["categoryId"]
db_sess.commit()
return jsonify({'success': 'OK'})
class DishListResource(Resource):
def get(self):
session = db_session.create_session()
categories = session.query(Dish).all()
return jsonify([item.to_dict() for item in categories])
def post(self):
args = parser.parse_args()
db_sess = db_session.create_session()
dish = Dish()
dish.name = args["name"]
dish.description = args["description"]
dish.image = args["image"].encode()
dish.userId = args["userId"]
dish.categoryId = args["categoryId"]
db_sess.add(dish)
db_sess.commit()
return jsonify({'success': 'OK'})
class DishUserListResource(Resource):
def get(self, user_id):
session = db_session.create_session()
categories = session.query(Dish).where(Dish.userId == user_id)
return jsonify([item.to_dict() for item in categories])

View File

@ -0,0 +1,11 @@
import sqlalchemy
from recources.db_session import SqlAlchemyBase
from sqlalchemy_serializer import SerializerMixin
class Category(SqlAlchemyBase, SerializerMixin):
__tablename__ = 'categories'
id = sqlalchemy.Column(sqlalchemy.Integer,
primary_key=True, autoincrement=True)
name = sqlalchemy.Column(sqlalchemy.String)

View File

@ -0,0 +1,15 @@
import sqlalchemy
from recources.db_session import SqlAlchemyBase
from sqlalchemy_serializer import SerializerMixin
class Dish(SqlAlchemyBase, SerializerMixin):
__tablename__ = 'dishes'
id = sqlalchemy.Column(sqlalchemy.Integer,
primary_key=True, autoincrement=True)
name = sqlalchemy.Column(sqlalchemy.String)
description = sqlalchemy.Column(sqlalchemy.String)
image = sqlalchemy.Column(sqlalchemy.LargeBinary)
userId = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("users.id"))
categoryId = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("categories.id"))

View File

@ -0,0 +1,21 @@
import sqlalchemy
from recources.db_session import SqlAlchemyBase
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy_serializer import SerializerMixin
class User(SqlAlchemyBase, UserMixin, SerializerMixin):
__tablename__ = 'users'
id = sqlalchemy.Column(sqlalchemy.Integer,
primary_key=True, autoincrement=True)
nickname = sqlalchemy.Column(sqlalchemy.String)
email = sqlalchemy.Column(sqlalchemy.String, unique=True)
hashed_password = sqlalchemy.Column(sqlalchemy.String)
def set_password(self, password):
self.hashed_password = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.hashed_password, password)

View File

@ -0,0 +1,10 @@
import sqlalchemy
from recources.db_session import SqlAlchemyBase
from sqlalchemy_serializer import SerializerMixin
class UserFavorites(SqlAlchemyBase, SerializerMixin):
__tablename__ = 'user_favorites'
userId = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("users.id"), primary_key=True)
dishId = sqlalchemy.Column(sqlalchemy.Integer, sqlalchemy.ForeignKey("dishes.id"), primary_key=True)

View File

@ -0,0 +1 @@
from . import Category, Dish, User, UserFavorites

View File

@ -0,0 +1,60 @@
from flask_restful import reqparse, abort, Resource, marshal_with, fields
from . import db_session
from recources.Model.UserFavorites import UserFavorites
from flask import jsonify
from .Model.Dish import Dish
def abort_if_user_favorites_not_found(user_id, dish_id):
db_sess = db_session.create_session()
user = db_sess.query(UserFavorites).get({
"userId": user_id,
"dishId": dish_id
})
if not user:
abort(404, message=f'User favorites {user_id} not found')
class UserFavoriteResource(Resource):
def get(self, user_id, dish_id):
abort_if_user_favorites_not_found(user_id)
db_sess = db_session.create_session()
users = db_sess.query(UserFavorites).get({
"userId": user_id,
"dishId": dish_id
})
return jsonify(
{'users_favorite': users.to_dict()}
)
def delete(self, user_id, dish_id):
abort_if_user_favorites_not_found(user_id)
db_sess = db_session.create_session()
user = db_sess.query(UserFavorites).get({
"userId": user_id,
"dishId": dish_id
})
db_sess.delete(user)
db_sess.commit()
return jsonify({'success': 'OK'})
def post(self, user_id, dish_id):
db_sess = db_session.create_session()
user = UserFavorites()
user.userId = user_id
user.dishId = dish_id
db_sess.add(user)
db_sess.commit()
return jsonify({"user": user.to_dict()})
class UsersFavoritesListResource(Resource):
def get(self, user_id):
session = db_session.create_session()
user = session.query(Dish).join(UserFavorites).where(UserFavorites.userId == user_id)
return jsonify([item.to_dict() for item in user])

View File

@ -0,0 +1,70 @@
from flask_restful import reqparse, abort, Resource, marshal_with, fields
from . import db_session
from recources.Model.User import User
from flask import jsonify
def abort_if_user_not_found(user_id):
db_sess = db_session.create_session()
user = db_sess.query(User).get(user_id)
if not user:
abort(404, message=f'User {user_id} not found')
parser = reqparse.RequestParser()
parser.add_argument('nickname', required=True)
parser.add_argument('email', required=True)
parser.add_argument('password', required=True)
class UserResource(Resource):
def get(self, user_id):
abort_if_user_not_found(user_id)
db_sess = db_session.create_session()
users = db_sess.query(User).get(user_id)
return jsonify(
users.to_dict()
)
def delete(self, user_id):
abort_if_user_not_found(user_id)
db_sess = db_session.create_session()
user = db_sess.query(User).get(user_id)
db_sess.delete(user)
db_sess.commit()
return jsonify({'success': 'OK'})
def put(self, user_id):
abort_if_user_not_found(user_id)
args = parser.parse_args()
db_sess = db_session.create_session()
user = db_sess.query(User).get(user_id)
user.nickname = args["nickname"]
user.email = args["email"]
user.set_password(args["password"])
db_sess.commit()
return jsonify({'success': 'OK'})
class UsersListResource(Resource):
def get(self):
session = db_session.create_session()
user = session.query(User).all()
return jsonify([item.to_dict() for item in user])
def post(self):
args = parser.parse_args()
db_sess = db_session.create_session()
user = User()
user.nickname = args["nickname"]
user.email = args["email"]
user.set_password(args["password"])
db_sess.add(user)
db_sess.commit()
return jsonify(user.to_dict())

View File

@ -0,0 +1,32 @@
import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.orm import Session
import sqlalchemy.ext.declarative as dec
SqlAlchemyBase = dec.declarative_base()
__factory = None
def global_init(db_file):
global __factory
if __factory:
return
if not db_file or not db_file.strip():
raise Exception("Необходимо указать файл базы данных.")
conn_str = f'sqlite:///{db_file.strip()}?check_same_thread=False'
print(f"Подключение к базе данных по адресу {conn_str}")
engine = sa.create_engine(conn_str, echo=False)
__factory = orm.sessionmaker(bind=engine)
from . import Model
SqlAlchemyBase.metadata.create_all(engine)
def create_session() -> Session:
global __factory
return __factory()

View File

@ -3,6 +3,7 @@ pluginManagement {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {