Added the profile feature
This commit is contained in:
@@ -16,6 +16,11 @@ import com.atridad.mealient.datasource.models.GetUserFavoritesResponse
|
||||
import com.atridad.mealient.datasource.models.GetUserInfoResponse
|
||||
import com.atridad.mealient.datasource.models.ParseRecipeURLRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateRecipeRequest
|
||||
import com.atridad.mealient.datasource.models.UserProfileResponse
|
||||
import com.atridad.mealient.datasource.models.UpdateUserProfileRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateUserResponse
|
||||
import com.atridad.mealient.datasource.models.ChangePasswordRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateProfileImageRequest
|
||||
import com.atridad.mealient.datasource.models.VersionResponse
|
||||
|
||||
interface MealieDataSource {
|
||||
@@ -83,4 +88,13 @@ interface MealieDataSource {
|
||||
suspend fun updateShoppingListName(id: String, name: String)
|
||||
|
||||
suspend fun getUserFavoritesAlternative(userId: String): GetUserFavoritesResponse
|
||||
|
||||
// User Profile Management
|
||||
suspend fun getUserProfile(): UserProfileResponse
|
||||
|
||||
suspend fun updateUserProfile(userId: String, request: UpdateUserProfileRequest): UpdateUserResponse
|
||||
|
||||
suspend fun changePassword(request: ChangePasswordRequest)
|
||||
|
||||
suspend fun updateProfileImage(userId: String, request: UpdateProfileImageRequest)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ import com.atridad.mealient.datasource.models.GetUserInfoResponse
|
||||
import com.atridad.mealient.datasource.models.ParseRecipeURLRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateRecipeRequest
|
||||
import com.atridad.mealient.datasource.models.VersionResponse
|
||||
import com.atridad.mealient.datasource.models.UserProfileResponse
|
||||
import com.atridad.mealient.datasource.models.UpdateUserProfileRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateUserResponse
|
||||
import com.atridad.mealient.datasource.models.ChangePasswordRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateProfileImageRequest
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
internal interface MealieService {
|
||||
@@ -73,4 +78,13 @@ internal interface MealieService {
|
||||
suspend fun getShoppingListJson(id: String): JsonElement
|
||||
|
||||
suspend fun getUserFavoritesAlternative(userId: String): GetUserFavoritesResponse
|
||||
|
||||
// User Profile Management
|
||||
suspend fun getUserProfile(): UserProfileResponse
|
||||
|
||||
suspend fun updateUserProfile(userId: String, request: UpdateUserProfileRequest): UpdateUserResponse
|
||||
|
||||
suspend fun changePassword(request: ChangePasswordRequest)
|
||||
|
||||
suspend fun updateProfileImage(userId: String, request: UpdateProfileImageRequest)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ import com.atridad.mealient.datasource.models.GetUserFavoritesResponse
|
||||
import com.atridad.mealient.datasource.models.GetUserInfoResponse
|
||||
import com.atridad.mealient.datasource.models.ParseRecipeURLRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateRecipeRequest
|
||||
import com.atridad.mealient.datasource.models.UserProfileResponse
|
||||
import com.atridad.mealient.datasource.models.UpdateUserProfileRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateUserResponse
|
||||
import com.atridad.mealient.datasource.models.ChangePasswordRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateProfileImageRequest
|
||||
import com.atridad.mealient.datasource.models.VersionResponse
|
||||
import io.ktor.client.call.NoTransformationFoundException
|
||||
import io.ktor.client.call.body
|
||||
@@ -330,4 +335,39 @@ constructor(
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// User Profile Management
|
||||
override suspend fun getUserProfile(): UserProfileResponse {
|
||||
val response = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.getUserProfile() },
|
||||
logMethod = { "getUserProfile" },
|
||||
)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
override suspend fun updateUserProfile(userId: String, request: UpdateUserProfileRequest): UpdateUserResponse {
|
||||
val response = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.updateUserProfile(userId, request) },
|
||||
logMethod = { "updateUserProfile" },
|
||||
logParameters = { "userId = $userId" }
|
||||
)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
override suspend fun changePassword(request: ChangePasswordRequest) {
|
||||
networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.changePassword(request) },
|
||||
logMethod = { "changePassword" },
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun updateProfileImage(userId: String, request: UpdateProfileImageRequest) {
|
||||
networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||
block = { service.updateProfileImage(userId, request) },
|
||||
logMethod = { "updateProfileImage" },
|
||||
logParameters = { "userId = $userId" }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,18 +18,27 @@ import com.atridad.mealient.datasource.models.GetUserFavoritesResponse
|
||||
import com.atridad.mealient.datasource.models.GetUserInfoResponse
|
||||
import com.atridad.mealient.datasource.models.ParseRecipeURLRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateRecipeRequest
|
||||
import com.atridad.mealient.datasource.models.UserProfileResponse
|
||||
import com.atridad.mealient.datasource.models.UpdateUserProfileRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateUserResponse
|
||||
import com.atridad.mealient.datasource.models.ChangePasswordRequest
|
||||
import com.atridad.mealient.datasource.models.UpdateProfileImageRequest
|
||||
import com.atridad.mealient.datasource.models.VersionResponse
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.forms.FormDataContent
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.patch
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.put
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.URLBuilder
|
||||
import io.ktor.http.contentType
|
||||
import io.ktor.http.parameters
|
||||
@@ -228,6 +237,47 @@ constructor(
|
||||
endpoint(baseUrl = baseUrl, path = path, block = block)
|
||||
}
|
||||
|
||||
// User Profile Management
|
||||
override suspend fun getUserProfile(): UserProfileResponse {
|
||||
return httpClient.get { endpoint("/api/users/self") }.body()
|
||||
}
|
||||
|
||||
override suspend fun updateUserProfile(userId: String, request: UpdateUserProfileRequest): UpdateUserResponse {
|
||||
return httpClient.put {
|
||||
endpoint("/api/users/$userId")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}.body()
|
||||
}
|
||||
|
||||
override suspend fun changePassword(request: ChangePasswordRequest) {
|
||||
httpClient.put {
|
||||
endpoint("/api/users/password")
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateProfileImage(userId: String, request: UpdateProfileImageRequest) {
|
||||
httpClient.post {
|
||||
endpoint("/api/users/$userId/image")
|
||||
setBody(
|
||||
MultiPartFormDataContent(
|
||||
formData {
|
||||
append(
|
||||
"profile_image",
|
||||
request.imageBytes,
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, request.mimeType)
|
||||
append(HttpHeaders.ContentDisposition, "filename=\"${request.fileName}\"")
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HttpRequestBuilder.endpoint(
|
||||
baseUrl: String,
|
||||
path: String,
|
||||
@@ -239,4 +289,6 @@ constructor(
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.atridad.mealient.datasource.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UserProfileResponse(
|
||||
@SerialName("id") val id: String,
|
||||
@SerialName("username") val username: String?,
|
||||
@SerialName("fullName") val fullName: String?,
|
||||
@SerialName("email") val email: String,
|
||||
@SerialName("authMethod") val authMethod: String? = null,
|
||||
@SerialName("admin") val admin: Boolean,
|
||||
@SerialName("group") val group: String? = null,
|
||||
@SerialName("household") val household: String? = null,
|
||||
@SerialName("advanced") val advanced: Boolean? = null,
|
||||
@SerialName("canInvite") val canInvite: Boolean? = null,
|
||||
@SerialName("canManage") val canManage: Boolean? = null,
|
||||
@SerialName("canManageHousehold") val canManageHousehold: Boolean? = null,
|
||||
@SerialName("canOrganize") val canOrganize: Boolean? = null,
|
||||
@SerialName("groupId") val groupId: String? = null,
|
||||
@SerialName("groupSlug") val groupSlug: String? = null,
|
||||
@SerialName("householdId") val householdId: String? = null,
|
||||
@SerialName("householdSlug") val householdSlug: String? = null,
|
||||
@SerialName("tokens") val tokens: List<ApiToken>? = null,
|
||||
@SerialName("cacheKey") val cacheKey: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ApiToken(
|
||||
@SerialName("name") val name: String,
|
||||
@SerialName("id") val id: Int,
|
||||
@SerialName("createdAt") val createdAt: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateUserResponse(
|
||||
@SerialName("message") val message: String,
|
||||
@SerialName("error") val error: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateUserProfileRequest(
|
||||
@SerialName("id") val id: String,
|
||||
@SerialName("username") val username: String?,
|
||||
@SerialName("fullName") val fullName: String?,
|
||||
@SerialName("email") val email: String,
|
||||
@SerialName("authMethod") val authMethod: String,
|
||||
@SerialName("admin") val admin: Boolean,
|
||||
@SerialName("group") val group: String?,
|
||||
@SerialName("household") val household: String?,
|
||||
@SerialName("advanced") val advanced: Boolean,
|
||||
@SerialName("canInvite") val canInvite: Boolean,
|
||||
@SerialName("canManage") val canManage: Boolean,
|
||||
@SerialName("canManageHousehold") val canManageHousehold: Boolean,
|
||||
@SerialName("canOrganize") val canOrganize: Boolean,
|
||||
@SerialName("groupId") val groupId: String?,
|
||||
@SerialName("groupSlug") val groupSlug: String?,
|
||||
@SerialName("householdId") val householdId: String?,
|
||||
@SerialName("householdSlug") val householdSlug: String?,
|
||||
@SerialName("tokens") val tokens: List<ApiToken>?,
|
||||
@SerialName("cacheKey") val cacheKey: String?,
|
||||
)
|
||||
|
||||
// Helper to create an update request from existing profile, preserving all permissions
|
||||
fun UserProfileResponse.toUpdateRequest(
|
||||
newFullName: String? = null,
|
||||
newEmail: String? = null,
|
||||
newUsername: String? = null
|
||||
): UpdateUserProfileRequest {
|
||||
return UpdateUserProfileRequest(
|
||||
id = this.id,
|
||||
username = newUsername ?: this.username,
|
||||
fullName = newFullName ?: this.fullName,
|
||||
email = newEmail ?: this.email,
|
||||
authMethod = this.authMethod ?: "Mealie",
|
||||
admin = this.admin, // Preserve existing admin status
|
||||
group = this.group,
|
||||
household = this.household,
|
||||
advanced = this.advanced ?: true,
|
||||
canInvite = this.canInvite ?: true,
|
||||
canManage = this.canManage ?: true,
|
||||
canManageHousehold = this.canManageHousehold ?: true,
|
||||
canOrganize = this.canOrganize ?: true,
|
||||
groupId = this.groupId,
|
||||
groupSlug = this.groupSlug,
|
||||
householdId = this.householdId,
|
||||
householdSlug = this.householdSlug,
|
||||
tokens = this.tokens,
|
||||
cacheKey = this.cacheKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ChangePasswordRequest(
|
||||
@SerialName("currentPassword") val currentPassword: String = "",
|
||||
@SerialName("newPassword") val newPassword: String,
|
||||
)
|
||||
|
||||
data class UpdateProfileImageRequest(
|
||||
val imageBytes: ByteArray,
|
||||
val fileName: String,
|
||||
val mimeType: String = "image/jpeg"
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as UpdateProfileImageRequest
|
||||
|
||||
if (!imageBytes.contentEquals(other.imageBytes)) return false
|
||||
if (fileName != other.fileName) return false
|
||||
if (mimeType != other.mimeType) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = imageBytes.contentHashCode()
|
||||
result = 31 * result + fileName.hashCode()
|
||||
result = 31 * result + mimeType.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user