Compare commits

...

10 Commits

Author SHA1 Message Date
bossanyit
8d45635684 v1.2.9.10 diet_user.macro 2023-06-28 21:44:00 +02:00
bossanyit
acc914f170 v1.2.9.4 fix on generated diet JSON handling 2023-06-15 17:14:19 +02:00
bossanyit
7ea9164f66 v1.2.9.3 fix on generated diet JSON handling 2023-06-15 16:31:22 +02:00
bossanyit
4362988c4a v1.2.9.2 fix on generated diet JSON handling 2023-06-15 07:38:55 +02:00
bossanyit
d2ef1415a1 v1.2.9 generate diet with meals on the server 2023-06-14 18:55:06 +02:00
bossanyit
8b0e6fe6c3 v1.2.8.1 premium diet generation modif 2023-05-18 08:18:57 +02:00
bossanyit
e3182c210c v1.2.8 premium diet generation endpoint 2023-05-11 15:37:52 +02:00
bossanyit
7a6dd693cb v1.2.7 memberships 2023-05-08 07:58:57 +02:00
bossanyit
a8d54fa78f v1.2.6.1 openai timeout 2023-05-04 08:05:09 +02:00
bossanyit
63c0dc5595 v1.2.6 diet.premium, diet registration email 2023-05-02 18:10:49 +02:00
44 changed files with 1007 additions and 91 deletions

2
.gitignore vendored
View File

@ -30,4 +30,4 @@ out/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
logs/aitrainer.log logs/

View File

@ -11,7 +11,7 @@ plugins {
} }
group = "com.aitrainer" group = "com.aitrainer"
version = "1.2.5" version = "1.2.10"
java.sourceCompatibility = JavaVersion.VERSION_17 java.sourceCompatibility = JavaVersion.VERSION_17
repositories { repositories {
@ -48,8 +48,10 @@ dependencies {
implementation("jakarta.mail:jakarta.mail-api:2.1.1") implementation("jakarta.mail:jakarta.mail-api:2.1.1")
implementation("org.eclipse.angus:angus-mail:2.0.1") implementation("org.eclipse.angus:angus-mail:2.0.1")
implementation ("com.aallam.openai:openai-client:3.2.2") implementation("com.aallam.openai:openai-client:3.2.2")
implementation("io.ktor:ktor-client-java:2.2.3") implementation("io.ktor:ktor-client-java:2.2.3")
implementation("ognl:ognl:3.3.4")
runtimeOnly("mysql:mysql-connector-java") runtimeOnly("mysql:mysql-connector-java")
testImplementation("org.springframework.boot:spring-boot-starter-test") { testImplementation("org.springframework.boot:spring-boot-starter-test") {

View File

@ -1 +0,0 @@
docker build --no-cache -t diet4you_api_test -f Dockerfile .

View File

@ -0,0 +1,7 @@
START TRANSACTION;
ALTER TABLE `diet_user` ADD COLUMN `macro` char(255) NULL;
UPDATE configuration set config_value = "1.2.10", date_change=CURRENT_DATE WHERE config_key = "db_version";
COMMIT;

8
data/db/update_1_2_6.sql Normal file
View File

@ -0,0 +1,8 @@
START TRANSACTION;
ALTER TABLE `diet`
ADD COLUMN `premium` TINYINT(1) NULL DEFAULT 0 AFTER `start_date`;
UPDATE configuration set config_value = "1.2.6", date_change=CURRENT_DATE WHERE config_key = "db_version";
COMMIT;

8
data/db/update_1_2_7.sql Normal file
View File

@ -0,0 +1,8 @@
START TRANSACTION;
ALTER TABLE `customer_membership`
ADD COLUMN `end_date` DATETIME NULL DEFAULT NULL AFTER `start_date`;
UPDATE configuration set config_value = "1.2.7", date_change=CURRENT_DATE WHERE config_key = "db_version";
COMMIT;

5
data/db/update_1_2_8.sql Normal file
View File

@ -0,0 +1,5 @@
START TRANSACTION;
UPDATE configuration set config_value = "1.2.8", date_change=CURRENT_DATE WHERE config_key = "db_version";
COMMIT;

7
data/db/update_1_2_9.sql Normal file
View File

@ -0,0 +1,7 @@
START TRANSACTION;
ALTER TABLE `diet` ADD COLUMN `generating` tinyint(1) NULL;
UPDATE configuration set config_value = "1.2.9", date_change=CURRENT_DATE WHERE config_key = "db_version";
COMMIT;

1
src/deploy/diet4you/build_api Executable file
View File

@ -0,0 +1 @@
docker buildx build --platform linux/amd64 --no-cache -t diet4you_api -f Dockerfile .

View File

@ -0,0 +1 @@
docker buildx build --platform linux/amd64 --no-cache -t diet4you_api_test -f Dockerfile .

2
src/deploy/diet4you/tag Normal file
View File

@ -0,0 +1,2 @@
docker tag diet4you_api_test:latest registry.workouttest.org/diet4you_api_test:v1.2.5.1
docker push registry.workouttest.org/diet4you_api_test:v1.2.5.1

View File

@ -3,12 +3,14 @@ package com.aitrainer.api.controller
import com.aitrainer.api.model.* import com.aitrainer.api.model.*
import com.aitrainer.api.service.ServiceBeans import com.aitrainer.api.service.ServiceBeans
import com.aitrainer.api.repository.CustomerRepository import com.aitrainer.api.repository.CustomerRepository
import com.aitrainer.api.repository.MembershipRepository
import com.aitrainer.api.service.Email import com.aitrainer.api.service.Email
import com.aitrainer.api.service.EmailTemplateService import com.aitrainer.api.service.EmailTemplateService
import com.aitrainer.api.service.Firebase import com.aitrainer.api.service.Firebase
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpHeaders import org.springframework.http.HttpHeaders
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.security.access.annotation.Secured import org.springframework.security.access.annotation.Secured
@ -22,7 +24,7 @@ import java.util.Base64
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
class CustomerController ( private val customerRepository: CustomerRepository) { class CustomerController (private val customerRepository: CustomerRepository, private val membershipRepository: MembershipRepository,) {
private val logger = LoggerFactory.getLogger(javaClass) private val logger = LoggerFactory.getLogger(javaClass)
@Autowired @Autowired
@ -325,4 +327,60 @@ class CustomerController ( private val customerRepository: CustomerRepository) {
ResponseEntity.badRequest().body("Customer does not exist or the password is wrong") ResponseEntity.badRequest().body("Customer does not exist or the password is wrong")
} }
} }
@PostMapping("/membership/{id}")
fun newMembership(@PathVariable(value = "id") membershipId: Long, @Valid @RequestBody customer: Customer): ResponseEntity<Customer> {
val returnCustomer: Customer = customerRepository.findById(customer.customerId).orElse(null)
?: return ResponseEntity.notFound().build()
println("found customer ${returnCustomer.customerId}")
val membership: Membership = membershipRepository.findByIdOrNull(membershipId)
?: return ResponseEntity.notFound().build()
println("found membership $membershipId")
val customerMembership = CustomerMembership()
customerMembership.membershipId = membershipId
customerMembership.customer = returnCustomer
val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
customerMembership.startDate = now.format(formatter)
returnCustomer.memberships.add(customerMembership)
customerRepository.save(returnCustomer)
val savedCustomer: Customer = customerRepository.findById(customer.customerId).orElse(null)
?: return ResponseEntity.notFound().build()
println("saved ${savedCustomer.customerId}")
return ResponseEntity.ok(savedCustomer)
}
@PostMapping("/membership/cancel/{id}")
fun cancelMembership(@PathVariable(value = "id") membershipId: Long, @Valid @RequestBody customer: Customer): ResponseEntity<Customer> {
val returnCustomer: Customer = customerRepository.findById(customer.customerId).orElse(null)
?: return ResponseEntity.notFound().build()
println("found customer ${returnCustomer.customerId}")
val membership: Membership = membershipRepository.findByIdOrNull(membershipId)
?: return ResponseEntity.notFound().build()
println("found membership $membershipId")
var found = false
var savedCustomer: Customer? = null
for ( customerMembership in returnCustomer.memberships ) {
if ( customerMembership.membershipId == membershipId) {
val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
customerMembership.endDate = now.format(formatter)
savedCustomer = customerRepository.save(returnCustomer)
found = true
}
}
return if ( ! found ) {
println("not found customer membership to cancel $membershipId")
ResponseEntity.notFound().build()
} else {
ResponseEntity.ok(savedCustomer)
}
}
} }

View File

@ -1,9 +1,12 @@
package com.aitrainer.api.controller package com.aitrainer.api.controller
import com.aallam.openai.api.BetaOpenAI import com.aallam.openai.api.BetaOpenAI
import com.aallam.openai.api.chat.ChatMessage
import com.aallam.openai.api.chat.ChatRole
import com.aitrainer.api.model.OpenAI import com.aitrainer.api.model.OpenAI
import com.aitrainer.api.model.OpenAIChat import com.aitrainer.api.model.OpenAIChat
import com.aitrainer.api.openai.OpenAIService import com.aitrainer.api.openai.OpenAIService
import com.google.gson.Gson
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
@ -72,5 +75,45 @@ class OpenAIController() {
} }
return result return result
} }
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
suspend fun chat(@Value("\${openai.key}") openaiKey: String, modelName: String, temperature: Double, userInput: String, systemInput: String = "") : String? {
val systemMsg = ChatMessage(
role = ChatRole.User,
content = systemInput
)
val userMsg = ChatMessage(
role = ChatRole.User,
content = userInput
)
val listMessages: MutableList<ChatMessage> = mutableListOf()
listMessages.add(systemMsg)
listMessages.add(userMsg)
val gson = Gson()
val messages = gson.toJson(listMessages)
val openai = OpenAIChat(
messages = messages,
modelName = modelName,
temperature = temperature
)
var response: String? = null
val openAIService = OpenAIService(openaiKey, openai.modelName, openai.temperature)
val deferred = GlobalScope.async {
println(openai.messages)
openAIService.chatCompletion(openai.messages)
}
runBlocking {
try {
response = deferred.await().toString()
} catch (exception: Exception) {
println("Timeout OpenAI chat")
}
}
return response
}
} }

View File

@ -1,17 +1,32 @@
package com.aitrainer.api.controller.diet package com.aitrainer.api.controller.diet
import com.aallam.openai.api.BetaOpenAI
import com.aitrainer.api.model.diet.Diet import com.aitrainer.api.controller.OpenAIController
import com.aitrainer.api.model.diet.*
import com.aitrainer.api.repository.diet.DietRepository import com.aitrainer.api.repository.diet.DietRepository
import com.aitrainer.api.repository.diet.MealRepository
import com.aitrainer.api.service.DietFunctions.getDateFromDayName
import com.aitrainer.api.service.DietFunctions.normalizeMealName
import com.aitrainer.api.service.Meals
import com.google.gson.Gson
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
class DietController(private val dietRepository: DietRepository) { class DietController(private val dietRepository: DietRepository, private val mealRepository: MealRepository) {
private val logger = LoggerFactory.getLogger(javaClass) private val logger = LoggerFactory.getLogger(javaClass)
private var openAiKey: String = ""
@PostMapping("/diet", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"]) @PostMapping("/diet", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"])
fun insert(@RequestBody diet: Diet): ResponseEntity<*> { fun insert(@RequestBody diet: Diet): ResponseEntity<*> {
@ -58,4 +73,404 @@ class DietController(private val dietRepository: DietRepository) {
return if (list.isEmpty()) ResponseEntity.notFound().build() else return if (list.isEmpty()) ResponseEntity.notFound().build() else
ResponseEntity.ok().body(list) ResponseEntity.ok().body(list)
} }
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
@PostMapping("/diet/generate_premium/{dietUserId}/{test}", produces = [MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8"])
fun generatePremiumDiet(@PathVariable dietUserId: Long, @PathVariable test: Boolean = false, @RequestBody input: String, @Value("\${openai.key}") openaiKey: String): ResponseEntity<*> {
val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
val todayString = now.format(formatter)
var diet: Diet? = dietRepository.findTheLastEmptyDiet(dietUserId)
val dietSaved: Diet?
this.openAiKey = openaiKey
if (diet == null) {
diet = Diet(
dietUserId = dietUserId,
dietText = "",
startDate = todayString,
premium = 0,
generating = 1
)
diet = dietRepository.save(diet)
var result: String?
if (!test) {
val systemInput =
"Te egy táplálkozási szakértő vagy. A feladat egy étrend elkészítése ebben a 3 lépésben:\n" +
"\n" +
"1. Vizsgáld meg az input adatokat, és készítsd el az étrendet\n" +
"2. Ellenőrizd az ételeket, hogy tényleg létezik-e olyan, és megfelel az input adatok feltételeinek\n" +
"3. Ellenőrizd a kimeneti formátumot. Csak a megadott JSON kimeneti formátumban válaszolj, semmilyen más formátum nem elfogadott.\n"
val deferred = GlobalScope.async {
OpenAIController().chat(
modelName = "gpt-4",
temperature = 0.9,
userInput = input,
systemInput = systemInput,
openaiKey = openaiKey
)
}
runBlocking {
result = deferred.await().toString()
result = extractJson(result!!)
println("extracted json: $result")
}
if (result == null || result!!.isEmpty()) {
return ResponseEntity.badRequest().build<String>()
}
} else {
result = "{\n" +
" \"DIET\": [\n" +
" {\n" +
" \"nameDay\": \"monday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"zabkása\", \"mandulatej\", \"málna\", \"méz\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"monday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"grillezett csirkemell\", \"quinoa\", \"spenót\", \"cherry paradicsom\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"monday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"sárgarépa\", \"hummusz\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"monday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"csicseriborsó-saláta\", \"paprika\", \"paradicsom\", \"uborka\", \"olívaolaj\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"tuesday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"rántotta\", \"sonka\", \"paradicsom\", \"gluténmentes kenyér\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"tuesday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"grillezett lazac\", \"barnarizs\", \"zöldbab\", \"citrom\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"tuesday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"natúr joghurt (laktózmentes)\", \"méz\", \"dió\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"tuesday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"paradicsomos quinoa\", \"spárga\", \"tonhal (konzerv)\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"wednesday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"zabkása\", \"mandulatej\", \"eper\", \"magok (napraforgó, tökmag)\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"wednesday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"töltött paprika\", \"rizs\", \"sovány darált pulykahús\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"wednesday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"cékla\", \"avokádó\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"wednesday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"kókusztejes csirke\", \"spenót\", \"barnarizs\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"thursday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"natúr joghurt (laktózmentes)\", \"mogyoróvaj\", \"banán\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"thursday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"görög saláta\", \"olívaolaj\", \"feta sajt (laktózmentes)\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"thursday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"mandula\", \"szárított füge\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"thursday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"grillezett hal\", \"brokkoli\", \"kukoricalisztből készült polenta\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"friday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"gluténmentes müzli\", \"mandulatej\", \"áfonya\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"friday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"lecsó\", \"kolbász (gluténmentes)\", \"gluténmentes kenyér\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"friday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"yoghurt (lactose free)\", \"honey\", \"walnut\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"friday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"grillezett padlizsán\", \"couscous(gluten free)\", \"csicseriborsó\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"saturday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"omlette\", \"sonka\", \"spenót\", \"gluténmentes kenyér\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"saturday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"húsgombóc\", \"paradicsomos mártás\", \"cukkinispirál\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"saturday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"narancs\", \"keksz (gluténmentes)\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"saturday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"saláta\", \"paradicsom\", \"uborka\", \"pulykamell\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"sunday\",\n" +
" \"mealTime\": \"breakfast\",\n" +
" \"meals\": [\"zabkása\", \"mandulatej\", \"mazsola\", \"mogyoró\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"sunday\",\n" +
" \"mealTime\": \"lunch\",\n" +
" \"meals\": [\"grillezett csirkemell\", \"háromszínű quinoa\", \"répa\", \"zöldborsó\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"sunday\",\n" +
" \"mealTime\": \"snack\",\n" +
" \"meals\": [\"natúr joghurt (laktózmentes)\", \"zabpehely\", \"banán\"]\n" +
" },\n" +
" {\n" +
" \"nameDay\": \"sunday\",\n" +
" \"mealTime\": \"dinner\",\n" +
" \"meals\": [\"töltött gomba\", \"couscous(gluten free)\", \"paradicsomsalsa\"]\n" +
" }\n" +
" ]\n" +
"}"
}
diet.dietText = result!!
diet.startDate = todayString
diet.premium = 1
dietSaved = dietRepository.save(diet)
} else {
dietSaved = diet
}
val gson = Gson()
val dietJson = JSONObject(diet.dietText)
val mealPlansJson = dietJson.getJSONArray("DIET")
val dietCustoms = mutableListOf<DietCustom>()
for (i in 0 until mealPlansJson.length()) {
val dietCustom = gson.fromJson(mealPlansJson.get(i).toString(), DietCustom::class.java)
dietCustoms.add(dietCustom)
}
dietCustoms.forEach { dietCustom ->
val dietCustomChanged = addTemporaryQuantity(dietCustom)
var index = 0
dietCustomChanged.meals.forEach {meal ->
val mealId: Long = getMealIdFromCustom(meal, dietCustomChanged, index, false, diet.dietUserId )
val dietMeal = DietMeal(
mealId = mealId,
meal = meal,
mealName = dietCustom.nameDay + "|" + dietCustom.mealTime,
mealDate = getDateFromDayName(dietCustom.nameDay, dietCustom.mealTime),
quantity = dietCustom.quantities[index],
)
dietMeal.diet = dietSaved
dietSaved.meals.add(dietMeal)
}
index++
}
dietSaved.generating = 0
val dietSavedWithMeals = dietRepository.save(dietSaved)
return ResponseEntity.ok().body(dietSavedWithMeals)
}
fun extractJson(input: String): String {
val jsonStart = input.indexOf("{")
val jsonEnd = input.lastIndexOf("}")
return if(jsonStart != -1 && jsonEnd != -1) {
input.substring(jsonStart, jsonEnd + 1)
} else {
""
}
}
fun addTemporaryQuantity(dietCustom: DietCustom): DietCustom {
for (i in 0 until dietCustom.meals.size) {
var sum = 400
when (dietCustom.mealTime) {
Meals.breakfast.toString() -> sum = 400
Meals.elevenses.toString() -> sum = 100
Meals.lunch.toString() -> sum = 500
Meals.snack.toString() -> sum = 150
Meals.dinner.toString() -> sum = 350
Meals.eveningsnack.toString() -> sum = 100
}
if (i == 0) {
dietCustom.quantities.add(sum * 0.7)
} else {
dietCustom.quantities.add(sum * 0.3 / (dietCustom.meals.size - 1))
}
}
return dietCustom
}
fun getMealIdFromCustom(mealName: String, dietCustom: DietCustom, mealIndex: Int, onlyQuantity: Boolean, dietUserId: Long): Long {
var mealId: Long = 0
var foundMeal: Meal? = mealRepository.findByName(mealName)
var dietCustomVar = dietCustom
if (foundMeal == null) {
dietCustomVar = addTemporaryQuantity(dietCustomVar)
if (!onlyQuantity) {
foundMeal = getMealCaloriesFromAI(mealName, dietCustomVar.quantities[mealIndex])
}
if (foundMeal != null) {
mealId = foundMeal.id
}
} else {
println("FoundMeal cals: ${foundMeal.calMin}")
mealId = foundMeal.id
}
return mealId
}
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
fun getMealCaloriesFromAI(mealName: String, quantity: Double): Meal? {
var meal: Meal? = null
val systemInput = "Te egy táplálkozási szakértő vagy. Feladat az ételek kalóriaértékének meghatározása"
val input = """
Add meg ennek az ételnek a kalóriaértékét és tápanyagfelosztását: $mealName 100 gramm.
A választ ebben JSON formátumban írd, ne fűzz semmilyen megjegyzést hozzá: {
"name":<string>,
"quantity":<double>,
"quantityUnit":<string>,
"calMin":<double>,
"calMax":<double>,
"fatMin":<double>,
"fatMax":<double>,
"chMin":<double>,
"chMax":<double>,
"proteinMin":<double>,
"proteinMax":<double>,
"sugar":<double>,
"servingUnit":<enum(db, szelet, adag, ml, liter)>
"serving":<double, servingUnit in grams> }
"""
val response: String?
val openaiKey = this.openAiKey
val deferred = GlobalScope.async {
OpenAIController().chat(
modelName = "gpt-3.5-turbo",
temperature = 0.0,
userInput = input,
systemInput = systemInput,
openaiKey = openaiKey)
}
runBlocking {
response = deferred.await().toString()
}
try {
val newMeal = Gson().fromJson(response, Meal::class.java)
val newMealName = newMeal.name
val found: Meal? = mealRepository.findByName(newMealName)
if (found == null) {
val baseQuantity = 100.0
val rate = quantity / baseQuantity
newMeal.quantity = baseQuantity
newMeal.calMin /= rate
newMeal.calMax /= rate
newMeal.chMin /= rate
newMeal.chMax /= rate
newMeal.fatMin /= rate
newMeal.fatMax /= rate
newMeal.proteinMin /= rate
newMeal.proteinMax /= rate
newMeal.sugar /= rate
newMeal.normalizedName = normalizeMealName(newMeal.name)
newMeal.serving = newMeal.serving
newMeal.servingUnit = newMeal.servingUnit
val savedMeal = mealRepository.save(newMeal)
println("save new meal: ${newMeal.name}, ${newMeal.calMin} kCal")
//updateServingForMeal(savedMeal)
meal = savedMeal
} else {
meal = found
}
} catch (e: Exception) {
println( "Error in newMeal decode JSON: '$e'")
}
return meal
}
@OptIn(BetaOpenAI::class, DelicateCoroutinesApi::class)
fun updateServingForMeal(meal: Meal) {
val input = """
Add meg, hány gramm egy adag "${meal.name}" átlagosan.
A választ pontosan ebben a formátumban kérem: {"meal": <string>, "serving":<double (average in gramms)>, "servingUnit": <enum (adag, szelet, db, ml, liter)>}.
The response MUST NOT contain other words just the JSON.
"""
val response: String?
val openaiKey = this.openAiKey
val deferred = GlobalScope.async {
OpenAIController().chat(modelName = "gpt-3.5-turbo", temperature = 0.0, userInput = input, openaiKey = openaiKey)
}
runBlocking {
response = deferred.await().toString()
}
val serving = Gson().fromJson(response, MealServing::class.java)
meal.serving = serving.serving
meal.servingUnit = serving.servingUnit
meal.normalizedName = normalizeMealName(meal.name) // placeholder for your function
mealRepository.save(meal)
}
} }

View File

@ -7,6 +7,8 @@ import com.aitrainer.api.model.diet.DietCustomer
import com.aitrainer.api.model.diet.DietUser import com.aitrainer.api.model.diet.DietUser
import com.aitrainer.api.repository.CustomerRepository import com.aitrainer.api.repository.CustomerRepository
import com.aitrainer.api.repository.diet.DietUserRepository import com.aitrainer.api.repository.diet.DietUserRepository
import com.aitrainer.api.service.Email
import com.aitrainer.api.service.EmailTemplateService
import com.aitrainer.api.service.Firebase import com.aitrainer.api.service.Firebase
import com.aitrainer.api.service.ServiceBeans import com.aitrainer.api.service.ServiceBeans
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
@ -24,6 +26,10 @@ class DietCustomerController(private val dietUserRepository: DietUserRepository,
@Autowired @Autowired
var serviceBeans: ServiceBeans? = null var serviceBeans: ServiceBeans? = null
@Autowired
private var emailTemplateService: EmailTemplateService? = null
@PostMapping("/diet_registration") @PostMapping("/diet_registration")
fun insert(@RequestBody dietCustomerJson: String, @Value("\${firebase.key}") apiKey: java.lang.String): ResponseEntity<*> { fun insert(@RequestBody dietCustomerJson: String, @Value("\${firebase.key}") apiKey: java.lang.String): ResponseEntity<*> {
val newDietCustomer: DietCustomer = DietCustomer().fromJson(dietCustomerJson) val newDietCustomer: DietCustomer = DietCustomer().fromJson(dietCustomerJson)
@ -120,17 +126,16 @@ class DietCustomerController(private val dietUserRepository: DietUserRepository,
idToken = existingCustomer.firebaseRegToken!! idToken = existingCustomer.firebaseRegToken!!
} }
// create email link
/*val activationLink = "https://diet4you.andio.hu/welcome/id=$idToken"
if ( emailTemplateService == null ) { if ( emailTemplateService == null ) {
emailTemplateService = EmailTemplateService() emailTemplateService = EmailTemplateService()
} }
val html = emailTemplateService!!.getEmailBody(newDietCustomer.firstname, activationLink, "diet_registration_email")
val html = emailTemplateService!!.getDietRegistrationEmailBody(newDietCustomer.firstname, newDietCustomer.email, "diet_registration_email")
val subject = emailTemplateService!!.getSubjectDiet() val subject = emailTemplateService!!.getSubjectDiet()
// send email // send email
val email = Email() val email = Email()
email.send(newDietCustomer.email, html, subject)*/ email.send("service@diet4you.eu", html, subject)
return ResponseEntity.ok().body(existingCustomer) return ResponseEntity.ok().body(existingCustomer)
} }

View File

@ -21,4 +21,11 @@ class DietUserController(private val dietUserRepository: DietUserRepository) {
return if (dietUser == null) ResponseEntity.notFound().build() else return if (dietUser == null) ResponseEntity.notFound().build() else
ResponseEntity.ok().body(dietUser) ResponseEntity.ok().body(dietUser)
} }
@PostMapping("/diet_user/{customerId}")
fun updateCustomer(@PathVariable customerId: Long, @RequestBody dietUser: DietUser): ResponseEntity<DietUser> {
dietUserRepository.findByCustomerId(customerId) ?: return ResponseEntity.notFound().build()
val changedUser = dietUserRepository.save(dietUser)
return ResponseEntity.ok().body(changedUser)
}
} }

View File

@ -10,6 +10,7 @@ data class CustomerMembership (
@Expose @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @get: NonNull var id: Long = 0, @Expose @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @get: NonNull var id: Long = 0,
@Expose @get: NonNull var membershipId: Long = 0, @Expose @get: NonNull var membershipId: Long = 0,
@Expose @get: NonNull var startDate: String? = null, @Expose @get: NonNull var startDate: String? = null,
@Expose @get: NonNull var endDate: String? = null,
) { ) {
@ManyToOne(fetch = FetchType.EAGER, optional = false) @ManyToOne(fetch = FetchType.EAGER, optional = false)
@JoinColumn(name = "customerId", nullable = false) @JoinColumn(name = "customerId", nullable = false)

View File

@ -10,8 +10,10 @@ import org.jetbrains.annotations.NotNull
data class Diet ( data class Diet (
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Expose val dietId: Long = 0, @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Expose val dietId: Long = 0,
@Expose @get: NotNull val dietUserId: Long = 0, @Expose @get: NotNull val dietUserId: Long = 0,
@Expose @get: NotNull val dietText: String = "", @Expose @get: NotNull var dietText: String = "",
@Expose @get: NotNull var startDate: String = "", @Expose @get: NotNull var startDate: String = "",
@Expose @get: NotNull var premium: Int = 0,
@Expose @get: NotNull var generating: Int = 0,
) { ) {
@OneToMany(cascade = [(CascadeType.ALL)], fetch = FetchType.EAGER, mappedBy = "diet") @OneToMany(cascade = [(CascadeType.ALL)], fetch = FetchType.EAGER, mappedBy = "diet")
@Fetch(value = FetchMode.SUBSELECT) @Fetch(value = FetchMode.SUBSELECT)

View File

@ -0,0 +1,17 @@
package com.aitrainer.api.model.diet
import com.google.gson.annotations.Expose
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import org.jetbrains.annotations.NotNull
@Entity
data class DietCustom(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Expose val id: Int = 0,
@Expose @get: NotNull val nameDay: String = "",
@Expose @get: NotNull val mealTime: String = "",
@Expose @get: NotNull val meals: List<String> = listOf(),
@Expose @get: NotNull val quantities: MutableList<Double> = mutableListOf()
)

View File

@ -10,5 +10,6 @@ import javax.validation.constraints.NotNull
@Entity @Entity
data class DietUser( data class DietUser(
@Expose @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var dietUserId: Long = 0, @Expose @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var dietUserId: Long = 0,
@Expose @get: NotNull val customerId: Long @Expose @get: NotNull val customerId: Long,
@Expose @get: NotNull var macro: String = "",
) )

View File

@ -9,18 +9,18 @@ data class Meal (
@Expose @get: NotNull val name: String = "", @Expose @get: NotNull val name: String = "",
@Expose @get: NotNull var normalizedName: String = "", @Expose @get: NotNull var normalizedName: String = "",
@Expose @get: NotNull val quantity: Double = 0.0, @Expose @get: NotNull var quantity: Double = 0.0,
@Expose @get: NotNull val quantityUnit: String = "", @Expose @get: NotNull val quantityUnit: String = "",
@Expose @get: NotNull var serving: Double = 0.0, @Expose @get: NotNull var serving: Double = 0.0,
@Expose @get: NotNull var servingUnit: String = "", @Expose @get: NotNull var servingUnit: String = "",
@Expose @get: NotNull val description: String = "", @Expose @get: NotNull val description: String = "",
@Expose @get: NotNull val calMin: Double = 0.0, @Expose @get: NotNull var calMin: Double = 0.0,
@Expose @get: NotNull val calMax: Double = 0.0, @Expose @get: NotNull var calMax: Double = 0.0,
@Expose @get: NotNull val proteinMin: Double = 0.0, @Expose @get: NotNull var proteinMin: Double = 0.0,
@Expose @get: NotNull val proteinMax: Double = 0.0, @Expose @get: NotNull var proteinMax: Double = 0.0,
@Expose @get: NotNull val fatMin: Double = 0.0, @Expose @get: NotNull var fatMin: Double = 0.0,
@Expose @get: NotNull val fatMax: Double = 0.0, @Expose @get: NotNull var fatMax: Double = 0.0,
@Expose @get: NotNull val chMin: Double = 0.0, @Expose @get: NotNull var chMin: Double = 0.0,
@Expose @get: NotNull val chMax: Double = 0.0, @Expose @get: NotNull var chMax: Double = 0.0,
@Expose @get: NotNull val sugar: Double = 0.0, @Expose @get: NotNull var sugar: Double = 0.0,
) )

View File

@ -0,0 +1,13 @@
package com.aitrainer.api.model.diet
import com.google.gson.annotations.Expose
import jakarta.persistence.*
import org.jetbrains.annotations.NotNull
@Entity
data class MealServing(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Expose val id: Int = 0,
@Expose @get: NotNull val meal: String = "",
@Expose @get: NotNull val serving: Double = 0.0,
@Expose @get: NotNull val servingUnit: String = "",
)

View File

@ -7,6 +7,7 @@ import com.aallam.openai.api.chat.ChatMessage
import com.aallam.openai.client.OpenAI import com.aallam.openai.client.OpenAI
import com.aallam.openai.api.completion.CompletionRequest import com.aallam.openai.api.completion.CompletionRequest
import com.aallam.openai.api.completion.TextCompletion import com.aallam.openai.api.completion.TextCompletion
import com.aallam.openai.api.http.Timeout
import com.aallam.openai.api.logging.LogLevel import com.aallam.openai.api.logging.LogLevel
import com.aallam.openai.api.model.Model import com.aallam.openai.api.model.Model
import com.aallam.openai.api.model.ModelId import com.aallam.openai.api.model.ModelId
@ -16,6 +17,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import kotlin.time.Duration.Companion.seconds
@Service @Service
class OpenAIService(@Value("\${openai.key}") private val openaiKey: String, private val modelName: String?, private val temperature: Double?) { class OpenAIService(@Value("\${openai.key}") private val openaiKey: String, private val modelName: String?, private val temperature: Double?) {
@ -26,7 +29,8 @@ class OpenAIService(@Value("\${openai.key}") private val openaiKey: String, priv
private suspend fun connect(modelName: String) { private suspend fun connect(modelName: String) {
val config = OpenAIConfig( val config = OpenAIConfig(
token = openaiKey, token = openaiKey,
logLevel = LogLevel.All logLevel = LogLevel.All,
timeout = Timeout(socket = 600.seconds)
) )
openAI = OpenAI(config) openAI = OpenAI(config)
modelId = ModelId(modelName) modelId = ModelId(modelName)

View File

@ -11,4 +11,7 @@ interface DietRepository : JpaRepository<Diet, Long> {
@Query(" FROM Diet WHERE dietId = :dietId") @Query(" FROM Diet WHERE dietId = :dietId")
fun findByDietId(dietId: Long): Diet? fun findByDietId(dietId: Long): Diet?
@Query(" FROM Diet WHERE ((LENGTH(dietText) = 0 AND premium = 1 ) OR (generating = 1)) AND dietUserId = :dietUserId ORDER BY dietId DESC LIMIT 1")
fun findTheLastEmptyDiet(dietUserId: Long): Diet?
} }

View File

@ -0,0 +1,170 @@
package com.aitrainer.api.service
import java.time.LocalDateTime
import java.util.*
enum class Meals {
breakfast,
elevenses,
lunch,
snack,
dinner,
eveningsnack;
companion object {
private fun Meals.enumToString(): String = this.toString().lowercase(Locale.getDefault())
fun Meals.equalsStringTo(mealName: String): Boolean = this.toString().lowercase(Locale.getDefault()) == mealName.lowercase(
Locale.getDefault()
)
fun getMealsByIndex(index: Int): String? {
for (meal in Meals.values()) {
if (index == meal.ordinal) {
return meal.enumToString()
}
}
return null
}
private fun Meals.getMealTimeHour(): Int {
return when (this) {
breakfast -> 7
elevenses -> 9
lunch -> 11
snack -> 14
dinner -> 17
eveningsnack -> 20
}
}
fun getHour(text: String): Int? {
for (meal in Meals.values()) {
if (text.lowercase(Locale.getDefault()) == meal.enumToString() || text.lowercase(Locale.getDefault())
.contains(meal.enumToString().lowercase(Locale.getDefault()))) {
return meal.getMealTimeHour()
}
}
return null
}
fun contains(text: String): Meals? {
for (meal in Meals.values()) {
if (text.lowercase(Locale.getDefault()).contains(meal.enumToString().lowercase(Locale.getDefault()))) {
return meal
}
}
return null
}
}
}
enum class WeekDays {
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday;
companion object WeekDaysExt {
private fun WeekDays.enumToString(): String {
return this.toString().lowercase(Locale.getDefault())
}
fun WeekDays.equalsStringTo(dayName: String): Boolean {
return this.toString().equals(dayName, ignoreCase = true)
}
private fun WeekDays.getWeekdayNumber(): Int {
return when (this) {
monday -> 1
tuesday -> 2
wednesday -> 3
thursday -> 4
friday -> 5
saturday -> 6
sunday -> 7
}
}
fun contains(text: String): WeekDays? {
var actualDay: WeekDays? = null
for (day in WeekDays.values()) {
if (text.contains(day.enumToString())) {
actualDay = day
break
}
}
return actualDay
}
fun weekDay(text: String): Int? {
var actualDay: Int? = null
for (day in WeekDays.values()) {
val weekDayHu = day.enumToString()
if (day.name.equals(text, true) || weekDayHu.equals(text, true)) {
actualDay = day.getWeekdayNumber()
break
}
}
return actualDay
}
}
}
object DietFunctions {
@Throws(Exception::class)
fun getDateFromDayName(dayName: String, mealName: String): String {
var dayNameVar = dayName
if (dayNameVar == "evening_snack") {
dayNameVar = "eveningSnack"
}
val today = LocalDateTime.now()
val weekDay = WeekDays.weekDay(dayNameVar)
val hour = Meals.getHour(mealName)
weekDay ?: throw Exception("$dayNameVar is not a week day")
hour ?: throw Exception("$mealName is not a meal")
val difference = today.dayOfWeek.value - weekDay
val day = today.minusDays(difference.toLong())
return "${day.year}-${day.monthValue}-${day.dayOfMonth} $hour:00"
}
fun normalizeMealName(hungarianWord: String): String {
val diacriticals: Map<Char, Char> = mapOf(
'á' to 'a',
'é' to 'e',
'í' to 'i',
'ó' to 'o',
'ö' to 'o',
'ő' to 'o',
'ú' to 'u',
'ü' to 'u',
'ű' to 'u',
'Á' to 'A',
'É' to 'E',
'Í' to 'I',
'Ó' to 'O',
'Ö' to 'O',
'Ő' to 'O',
'Ú' to 'U',
'Ü' to 'U',
'Ű' to 'U',
'-' to ' ',
',' to ' ',
)
var englishWord = ""
for (element in hungarianWord) {
englishWord += diacriticals[element] ?: element
}
return englishWord.lowercase(Locale.getDefault())
}
}

View File

@ -21,6 +21,17 @@ class EmailTemplateService {
return templateEngine!!.process(template, context) return templateEngine!!.process(template, context)
} }
fun getDietRegistrationEmailBody(firstname: String, email: String, template: String): String {
val context = Context()
context.setVariable("customerName", firstname)
context.setVariable("customerEmail", email)
if ( templateEngine == null) {
templateEngine = TemplateEngine()
}
return templateEngine!!.process(template, context)
}
fun getSubject(): String { fun getSubject(): String {
val context = Context() val context = Context()
if ( templateEngine == null) { if ( templateEngine == null) {

View File

@ -16,7 +16,7 @@ logging.config=classpath:logback-spring.xml
logging.file=logs logging.file=logs
# if the database structure has been changed, increment this version number # if the database structure has been changed, increment this version number
application.version=1.2.5 application.version=1.2.10
jwt.secret=aitrainer jwt.secret=aitrainer

View File

@ -14,7 +14,7 @@ logging.config=classpath:logback-spring.xml
logging.file=logs logging.file=logs
# if the database structue has been changed, increment this version number # if the database structue has been changed, increment this version number
application.version=1.2.5 application.version=1.2.10
jwt.secret=aitrainer jwt.secret=aitrainer

View File

@ -1,7 +0,0 @@
#spring.config.activate.on-profile=dev,test,prod,prodtest
spring.config.use-legacy-processing = true
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://192.168.100.98:3306/diet4you?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.username = aitrainer
spring.datasource.password = ENC(WZplPYr8WmrLHshesY4T6oXplK3MlUVJ)
spring.http.encoding.charset=UTF-8

View File

@ -14,7 +14,7 @@ logging.config=classpath:logback-spring.xml
logging.file=logs logging.file=logs
# if the database structue has been changed, increment this version number # if the database structue has been changed, increment this version number
application.version=1.2.5 application.version=1.2.10
jwt.secret=aitrainer jwt.secret=aitrainer

View File

@ -1,14 +0,0 @@
spring.config.activate.on-profile=testmac
spring.config.use-legacy-processing = true
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
#spring.datasource.url = jdbc:mysql://localhost:3306/aitrainer?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/aitrainer2?serverTimezone=CET&useSSL=false&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true
spring.datasource.username = root
spring.datasource.password = ENC(WZplPYr8WmrLHshesY4T6oXplK3MlUVJ)
## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect

View File

@ -17,7 +17,7 @@ logging.config=classpath:logback-spring.xml
logging.file=logs logging.file=logs
# if the database structure has been changed, increment this version number # if the database structure has been changed, increment this version number
application.version=1.2.5 application.version=1.2.10
jwt.secret=aitrainer jwt.secret=aitrainer

View File

@ -4,22 +4,19 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head> </head>
<body> <body>
<p th:text="'Szia ' + ${firstname} + '!'">Szia [firstname]!</p>
<p> <p>
Üdvözlünk a Diet 4 You (Diéta Neked) tagjai között! Örülünk, hogy velünk vagy. Az étrended a Diet4You zárt felületén tudod elérni. A belépéshez használd ezt a linket:<br/><br/> New Diet4You user:
<a th:href="${activationLink}" th:text="${activationLink}">${activationLink}</a>
</p> </p>
<p> <p>
Kérlek, kattints a linkre a fiókod aktiválásához. Ha bármilyen problémád van, ne habozz velünk kapcsolatba lépni. Email:<span th:text="${customerEmail}">Email</span><br/>
</p> Name:<span th:text="${customerName}">Name</span>
<p>
Köszönjük, hogy velünk dolgozol.
</p> </p>
<p> <p>
Üdvözlettel,<br> Üdvözlettel,<br>
Diéta Neked Csapata<br/> Diet4You Team<br/>
Diet 4 You Team<br/> mailto: service@diet4you.eu<br/>
mailto: diet4you@andio.hu<br/>
</p> </p>
</body> </body>
</html> </html>

View File

@ -1 +1 @@
[Diet4You] Üdv a céltudatosok között! [Diet4You] New Diet User!

View File

@ -394,7 +394,7 @@ class AppPackageTest {
val appTextJson: String = record[1] val appTextJson: String = record[1]
val type = object : TypeToken<List<AppText?>?>() {}.type val type = object : TypeToken<List<AppText?>?>() {}.type
val texts: List<AppText> = gson.fromJson(appTextJson, type) val texts: List<AppText> = gson.fromJson(appTextJson, type)
assertEquals(texts.size, 15) assertEquals(texts.size, 33)
assertEquals(texts[13].translations[0].translation, "Done!") assertEquals(texts[13].translations[0].translation, "Done!")
assertEquals(texts[13].translations[1].translation, "Kész!") assertEquals(texts[13].translations[1].translation, "Kész!")
} else if (record[0] == TrainingProgram::class.simpleName) { } else if (record[0] == TrainingProgram::class.simpleName) {

View File

@ -4,7 +4,9 @@ import com.aitrainer.api.controller.CustomerController
import com.aitrainer.api.model.Customer import com.aitrainer.api.model.Customer
import com.aitrainer.api.model.User import com.aitrainer.api.model.User
import com.aitrainer.api.repository.CustomerRepository import com.aitrainer.api.repository.CustomerRepository
import com.aitrainer.api.repository.MembershipRepository
import com.aitrainer.api.service.ServiceBeans import com.aitrainer.api.service.ServiceBeans
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.gson.Gson import com.google.gson.Gson
import org.json.JSONObject import org.json.JSONObject
import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeAll
@ -22,12 +24,13 @@ import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.MvcResult import org.springframework.test.web.servlet.MvcResult
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
import java.util.Base64
@SpringBootTest @SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -48,6 +51,9 @@ class CustomerTests {
@Autowired @Autowired
private lateinit var customerRepository: CustomerRepository private lateinit var customerRepository: CustomerRepository
@Autowired
private lateinit var membershipRepository: MembershipRepository
private var insertedId: Long? = null private var insertedId: Long? = null
@ -58,7 +64,7 @@ class CustomerTests {
assertEquals( customer.name, "Átlag 18 éves fiú") assertEquals( customer.name, "Átlag 18 éves fiú")
val id2: Long = 90 val id2: Long = 90
val controller = CustomerController(customerRepository) val controller = CustomerController(customerRepository, membershipRepository)
val response = controller.getCustomerById(id2) val response = controller.getCustomerById(id2)
val customer2: Customer = response.body as Customer val customer2: Customer = response.body as Customer
@ -111,7 +117,7 @@ class CustomerTests {
fun testDeactivateCustomer() { fun testDeactivateCustomer() {
val id: Long = 90 val id: Long = 90
val controller = CustomerController(customerRepository) val controller = CustomerController(customerRepository, membershipRepository)
controller.deactivateCustomer(id) controller.deactivateCustomer(id)
val customer: Customer = customerRepository.findById(id).orElse(null) val customer: Customer = customerRepository.findById(id).orElse(null)
@ -128,7 +134,7 @@ class CustomerTests {
@Test @Test
fun testFindByEmail() { fun testFindByEmail() {
val controller = CustomerController(customerRepository) val controller = CustomerController(customerRepository, membershipRepository)
var response = controller.getCustomerByEmail("sw@andio.biz") var response = controller.getCustomerByEmail("sw@andio.biz")
val customer = response.body val customer = response.body
@ -204,7 +210,7 @@ class CustomerTests {
customer.birthYear = 1972 customer.birthYear = 1972
customer.dateChange = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) customer.dateChange = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
val customerController = CustomerController(customerRepository) val customerController = CustomerController(customerRepository, membershipRepository)
var response: ResponseEntity<*> = customerController.updateCustomerById(id, customer, HttpHeaders.readOnlyHttpHeaders(HttpHeaders.EMPTY) ) var response: ResponseEntity<*> = customerController.updateCustomerById(id, customer, HttpHeaders.readOnlyHttpHeaders(HttpHeaders.EMPTY) )
print ("body " + response.body) print ("body " + response.body)
var newCustomer: Customer? = response.body as Customer var newCustomer: Customer? = response.body as Customer
@ -249,7 +255,7 @@ class CustomerTests {
@Test @Test
fun testClubRegistration(@Value("\${firebase.key}") apiKey: java.lang.String) { fun testClubRegistration(@Value("\${firebase.key}") apiKey: java.lang.String) {
val json = "{\"firstname\":\"Tib\", \"email\": \"mr@andio.biz\", \"goal\": \"shape\", \"fitnessLevel\": \"advanced\", \"weight\": 85}" val json = "{\"firstname\":\"Tib\", \"email\": \"mr@andio.biz\", \"goal\": \"shape\", \"fitnessLevel\": \"advanced\", \"weight\": 85}"
val controller = CustomerController(customerRepository) val controller = CustomerController(customerRepository, membershipRepository)
val response: ResponseEntity<*> = controller.clubRegistration(json, apiKey) val response: ResponseEntity<*> = controller.clubRegistration(json, apiKey)
assertEquals(response.statusCode, HttpStatus.BAD_REQUEST) assertEquals(response.statusCode, HttpStatus.BAD_REQUEST)
} }
@ -265,7 +271,7 @@ class CustomerTests {
password = user.password password = user.password
firebaseUid = user.firebaseUid firebaseUid = user.firebaseUid
} }
val customerController = CustomerController(customerRepository) val customerController = CustomerController(customerRepository, membershipRepository)
customerController.serviceBeans = serviceBean customerController.serviceBeans = serviceBean
val response: ResponseEntity<*> = customerController.registration(json) val response: ResponseEntity<*> = customerController.registration(json)
print("body " + response.body) print("body " + response.body)
@ -290,7 +296,7 @@ class CustomerTests {
insertedId = savedCustomer.customerId insertedId = savedCustomer.customerId
val customerController = CustomerController(customerRepository) val customerController = CustomerController(customerRepository, membershipRepository)
val response: ResponseEntity<*> = customerController.updateCustomerFirebaseUidById(insertedId!!, "3FirebusaeId4") val response: ResponseEntity<*> = customerController.updateCustomerFirebaseUidById(insertedId!!, "3FirebusaeId4")
val newCustomer2: Customer = response.body as Customer val newCustomer2: Customer = response.body as Customer
assertEquals(response.statusCode, HttpStatus.OK) assertEquals(response.statusCode, HttpStatus.OK)
@ -305,7 +311,7 @@ class CustomerTests {
@Test @Test
fun testGetCustomerByFirebaseUid() { fun testGetCustomerByFirebaseUid() {
val uid = "3FirebaseU1d" val uid = "3FirebaseU1d"
val customerController = CustomerController(customerRepository) val customerController = CustomerController(customerRepository, membershipRepository)
val response: ResponseEntity<*> = customerController.getCustomerByFirebaseUid(uid) val response: ResponseEntity<*> = customerController.getCustomerByFirebaseUid(uid)
assertEquals(response.statusCode, HttpStatus.OK) assertEquals(response.statusCode, HttpStatus.OK)
val newCustomer: Customer = response.body as Customer val newCustomer: Customer = response.body as Customer
@ -317,7 +323,7 @@ class CustomerTests {
@Test @Test
fun testGetCustomerByEmail() { fun testGetCustomerByEmail() {
val email = "sw2@andio.biz" val email = "sw2@andio.biz"
val customerController = CustomerController(customerRepository) val customerController = CustomerController(customerRepository, membershipRepository)
val response: ResponseEntity<*> = customerController.getCustomerByEmail(email) val response: ResponseEntity<*> = customerController.getCustomerByEmail(email)
assertEquals(response.statusCode, HttpStatus.OK) assertEquals(response.statusCode, HttpStatus.OK)
@ -330,7 +336,7 @@ class CustomerTests {
} }
@Test @Test
fun `get customer successfully`() { fun `insert customer successfully`() {
authToken = Tokenizer.getToken() authToken = Tokenizer.getToken()
val customer = Customer( val customer = Customer(
@ -347,7 +353,7 @@ class CustomerTests {
.header("Authorization", "Bearer $authToken") .header("Authorization", "Bearer $authToken")
.content(toJson(customer)) .content(toJson(customer))
) )
.andExpect(MockMvcResultMatchers.status().isOk) .andExpect(status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Kadarka")) .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Kadarka"))
.andExpect(MockMvcResultMatchers.jsonPath("$.age").value(32)) .andExpect(MockMvcResultMatchers.jsonPath("$.age").value(32))
.andExpect(MockMvcResultMatchers.jsonPath("$.birthYear").value(1987)) .andExpect(MockMvcResultMatchers.jsonPath("$.birthYear").value(1987))
@ -376,7 +382,7 @@ class CustomerTests {
.header("Authorization", "Bearer $authToken") .header("Authorization", "Bearer $authToken")
.content(toJson(user)) .content(toJson(user))
) )
.andExpect(MockMvcResultMatchers.status().isOk) .andExpect(status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Bos")) .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Bos"))
.andExpect(MockMvcResultMatchers.jsonPath("$.firstname").value("Kakadu")) .andExpect(MockMvcResultMatchers.jsonPath("$.firstname").value("Kakadu"))
.andExpect(MockMvcResultMatchers.jsonPath("$.birthYear").value(1972)) .andExpect(MockMvcResultMatchers.jsonPath("$.birthYear").value(1972))
@ -400,7 +406,7 @@ class CustomerTests {
.header("Authorization", "Bearer $authToken") .header("Authorization", "Bearer $authToken")
.content(password) .content(password)
) )
.andExpect(MockMvcResultMatchers.status().isOk) .andExpect(status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Bos")) .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("Bos"))
.andExpect(MockMvcResultMatchers.jsonPath("$.firstname").value("Kakadu")) .andExpect(MockMvcResultMatchers.jsonPath("$.firstname").value("Kakadu"))
.andExpect(MockMvcResultMatchers.jsonPath("$.birthYear").value(1972)) .andExpect(MockMvcResultMatchers.jsonPath("$.birthYear").value(1972))
@ -417,10 +423,58 @@ class CustomerTests {
.header("Authorization", "Bearer $authToken") .header("Authorization", "Bearer $authToken")
.content(password) .content(password)
) )
.andExpect(MockMvcResultMatchers.status().isOk) .andExpect(status().isOk)
}
@Test
fun `new membership successfully`() {
authToken = Tokenizer.getToken()
val gson= Gson()
val customer = getCustomer(103)
mockMvc.perform(
MockMvcRequestBuilders.post("/api/membership/89")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer $authToken")
.content(toJson(customer))
).andExpect(status().isNotFound)
val mvcResult2: MvcResult = mockMvc.perform(
MockMvcRequestBuilders.post("/api/membership/2")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer $authToken")
.content(toJson(customer))
).andExpect(status().isOk)
//.andExpect(MockMvcResultMatchers.jsonPath("$.memberships.length").value(3))
.andReturn()
val customer2Json = mvcResult2.response.contentAsString
println(customer2Json)
val customer2 = gson.fromJson(customer2Json, Customer::class.java)
println(customer2)
}
fun getCustomer(customerId: Long) : Customer {
authToken = Tokenizer.getToken()
val mvcResult: MvcResult = mockMvc.perform(
MockMvcRequestBuilders.get("/api/customers/$customerId")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer $authToken")
)
.andExpect(status().isOk)
.andReturn()
val gson= Gson()
val customerJson = mvcResult.response.contentAsString
return gson.fromJson(customerJson, Customer::class.java)
} }
private fun toJson(obj: Any): String { private fun toJson(obj: Any): String {
return Gson().toJson(obj) return Gson().toJson(obj)
} }
} }

View File

@ -31,8 +31,8 @@ class EmailTest {
assertTrue (html.isNotEmpty()) assertTrue (html.isNotEmpty())
assertTrue(html.contains("Tibor")) assertTrue(html.contains("Tibor"))
} }
@Test
fun testSend() { fun rtestSend() {
try { try {
email.send("tibor.bossanyi@aitrainer.app", "<h1>Üdv</h1><p>id=932989</p>", "Workout Test API test") email.send("tibor.bossanyi@aitrainer.app", "<h1>Üdv</h1><p>id=932989</p>", "Workout Test API test")
@ -41,8 +41,14 @@ class EmailTest {
println("Error sending email: $e") println("Error sending email: $e")
assertTrue(false) assertTrue(false)
} }
}
@Test
fun testDietReg() {
val html = emailTemplateService.getDietRegistrationEmailBody("Tibor", "sw@aitrainer.app", "diet_registration_email")
val subject = emailTemplateService.getSubjectDiet()
// send email
email.send("service@diet4you.eu", html, subject)
} }
} }

View File

@ -2,6 +2,7 @@ package com.aitrainer.api.test.diet
import com.aitrainer.api.model.diet.Diet import com.aitrainer.api.model.diet.Diet
import com.aitrainer.api.model.diet.DietMeal import com.aitrainer.api.model.diet.DietMeal
import com.aitrainer.api.repository.diet.DietRepository
import com.aitrainer.api.test.Tokenizer import com.aitrainer.api.test.Tokenizer
import com.google.gson.Gson import com.google.gson.Gson
import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeAll
@ -18,6 +19,8 @@ import org.springframework.test.web.servlet.MvcResult
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.* import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
@ExtendWith(SpringExtension::class) @ExtendWith(SpringExtension::class)
@ -29,11 +32,16 @@ class DietTest {
@Autowired @Autowired
private lateinit var mockMvc: MockMvc private lateinit var mockMvc: MockMvc
@Autowired
private lateinit var dietRepository: DietRepository
private val diet = Diet( private val diet = Diet(
dietUserId = 1, dietUserId = 1,
dietText = "Test diet text", dietText = "Test diet text",
startDate = "2023-02-02" startDate = "2023-02-02",
premium = 0,
generating = 0
) )
private var authToken: String? = "" private var authToken: String? = ""
@ -120,7 +128,6 @@ class DietTest {
.andExpect(jsonPath("$.meals[2].servingUnit").value("adag")) .andExpect(jsonPath("$.meals[2].servingUnit").value("adag"))
// update no_text // update no_text
newDiet.startDate = "2023-03-20" newDiet.startDate = "2023-03-20"
newDiet.meals[0].quantity = 102.0 newDiet.meals[0].quantity = 102.0
mockMvc.perform( mockMvc.perform(
@ -134,6 +141,74 @@ class DietTest {
.andExpect(jsonPath("$.meals[0].quantity").value(102.0)) .andExpect(jsonPath("$.meals[0].quantity").value(102.0))
.andExpect(jsonPath("$.dietText").value("Test diet text")) .andExpect(jsonPath("$.dietText").value("Test diet text"))
val diet2 = Diet(
dietUserId = 2,
dietText = "Premium Test",
startDate = "2023-05-02",
premium = 1,
generating = 0
)
val mvcResult2: MvcResult = mockMvc.perform(
MockMvcRequestBuilders.post("/api/diet")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer $authToken")
.content(toJson(diet2))
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.dietUserId").value(2))
.andExpect(jsonPath("$.dietText").value("Premium Test"))
.andExpect(jsonPath("$.premium").value(1))
.andReturn()
val newDietJson2 = mvcResult2.response.contentAsString
val newDiet2 = gson.fromJson(newDietJson2, Diet::class.java)
dietRepository.delete(newDiet)
dietRepository.delete(newDiet2)
}
@Test
fun `find the last empty diet successfully`() {
val diet: Diet? = dietRepository.findTheLastEmptyDiet(2)
assertNotNull(diet)
assertEquals(diet.premium, 1)
println("DietID: ${diet.dietId}")
}
//@Test
fun `generate premium diet successfully`() {
val input = "Készíts egy személyre szabott heti étrendet ezekkel a paraméterekkel:\n" +
"Férfi, 50 éves, 120kg, 170 magas, célja fogyás.\n" +
"- Speciális étrend: ketogén\n" +
"- Allergiák: olajos magvak\n" +
"- Preferált ételek: magyar konyha\n" +
"- Ezek az ételek nem szerepelhetnek az étrendben: főzelékek\n" +
//"- napi kalóriacél: 2100 kCal\n" +
"- Étel frekvencia: reggeli, ebéd, uzsonna, vacsora\n" +
"- Ebéd és vacsora megegyezik. \n" +
"- ne legyen ismétlődés az ételeknél\n" +
"\n" +
"A választ ebben a formátumban add meg:\n" +
"{ \"DIET\": [\n" +
"{\"nameDay\": enum<monday, tuesday, wednesday...>\n" +
"\"mealTime\":enum<breakfast, lunch, snack, dinner>\n" +
"\"meals\":[<mealNames in Hungarian>],\n" +
//"\"quantities\":[<quantities for each mealName in gramms>],\n" +
//"\"calories\":[<calorie values for each mealName>],\n" +
"}" +
"]}"
mockMvc.perform(
MockMvcRequestBuilders.post("/api/diet/generate_premium/2/true")
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer $authToken")
.content(input)
)
.andExpect(status().isOk)
.andExpect(jsonPath("$.dietUserId").value(2))
.andExpect(jsonPath("$.premium").value(1))
} }

View File

@ -50,6 +50,21 @@ class DietUserTest {
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk) .andExpect(status().isOk)
.andExpect(jsonPath("$.customerId").value(1)) .andExpect(jsonPath("$.customerId").value(1))
dietUser.macro = "Test macro"
mockMvc.perform(post("/api/diet_user/999")
.header("Authorization", "Bearer $authToken")
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(dietUser)))
.andExpect(status().isNotFound)
mockMvc.perform(post("/api/diet_user/${dietUser.customerId}")
.header("Authorization", "Bearer $authToken")
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(dietUser)))
.andExpect(status().isOk)
.andExpect(jsonPath("$.customerId").value(1))
.andExpect(jsonPath("$.macro").value("Test macro"))
} }
} }

View File

@ -138,11 +138,11 @@ class MealTest {
.header("Authorization", "Bearer $authToken") .header("Authorization", "Bearer $authToken")
.contentType(MediaType.APPLICATION_JSON)) .contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk) .andExpect(status().isOk)
.andExpect(jsonPath("$.[3].name").value("Tükörtojás")) .andExpect(jsonPath("$.[111].name").value("Tükörtojás"))
.andExpect(jsonPath("$.[3].normalizedName").value("Tukortojas")) .andExpect(jsonPath("$.[111].normalizedName").value("Tukortojas"))
.andExpect(jsonPath("$.[4].name").value("Tigris buci")) .andExpect(jsonPath("$.[112].name").value("Tigris buci"))
.andExpect(jsonPath("$.[5].quantity").value(330.0)) .andExpect(jsonPath("$.[113].quantity").value(330.0))
.andExpect(jsonPath("$.[5].name").value("Töltötttojás")) .andExpect(jsonPath("$.[113].name").value("Töltötttojás"))
// Act & Assert // Act & Assert

View File

@ -54,7 +54,7 @@ class OpenAITest {
}*/ }*/
@OptIn(BetaOpenAI::class) @OptIn(BetaOpenAI::class)
@Test //@Test
fun `get a chat message completion`() { fun `get a chat message completion`() {
val systemMsg = ChatMessage( val systemMsg = ChatMessage(
role = ChatRole.User, role = ChatRole.User,
@ -86,7 +86,7 @@ class OpenAITest {
} }
@OptIn(BetaOpenAI::class) @OptIn(BetaOpenAI::class)
@Test // @Test
fun `get a chat message completion no modelname`() { fun `get a chat message completion no modelname`() {
val systemMsg = ChatMessage( val systemMsg = ChatMessage(
role = ChatRole.User, role = ChatRole.User,
@ -115,7 +115,7 @@ class OpenAITest {
} }
@Test // @Test
fun `get a question successfully with model name`() { fun `get a question successfully with model name`() {
val question = "Who the f. is Alice? Who sing that song?" val question = "Who the f. is Alice? Who sing that song?"
val openai = OpenAI( val openai = OpenAI(
@ -134,7 +134,7 @@ class OpenAITest {
} }
@Test // @Test
fun `get models successfully`() { fun `get models successfully`() {
mockMvc.perform( mockMvc.perform(
MockMvcRequestBuilders.get("/api/openai/list_models") MockMvcRequestBuilders.get("/api/openai/list_models")