From: Shane Jaroch Date: Mon, 26 Jan 2026 11:37:54 +0000 (-0500) Subject: wip keep going X-Git-Url: https://git.nutra.tk/v1?a=commitdiff_plain;h=refs%2Fheads%2Fdev;p=nutratech%2Fgui.git wip keep going --- diff --git a/.github/workflows/ci-full.yml b/.github/workflows/ci-full.yml index 2ae5b29..943a8cf 100644 --- a/.github/workflows/ci-full.yml +++ b/.github/workflows/ci-full.yml @@ -37,6 +37,4 @@ jobs: run: make release - name: Test - env: - QT_QPA_PLATFORM: offscreen run: make test diff --git a/.github/workflows/ubuntu-24.04.yml b/.github/workflows/ubuntu-24.04.yml index 81ff126..c20728d 100644 --- a/.github/workflows/ubuntu-24.04.yml +++ b/.github/workflows/ubuntu-24.04.yml @@ -30,8 +30,6 @@ jobs: run: make release - name: Test - env: - QT_QPA_PLATFORM: offscreen run: make test - name: Upload Artifact diff --git a/CMakeLists.txt b/CMakeLists.txt index f523a11..69baf5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,11 +15,13 @@ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Sql) # Sources -file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS "src/*.cpp") -file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS "include/*.h") +file(GLOB_RECURSE CORE_SOURCES_CPP "src/db/*.cpp" "src/utils/*.cpp") +file(GLOB_RECURSE CORE_HEADERS "include/db/*.h" "include/utils/*.h") +set(CORE_SOURCES ${CORE_SOURCES_CPP} ${CORE_HEADERS}) -# Filter out main.cpp for the library -list(FILTER SOURCES EXCLUDE REGEX ".*src/main\\.cpp$") +file(GLOB_RECURSE UI_SOURCES_CPP "src/widgets/*.cpp" "src/mainwindow.cpp") +file(GLOB_RECURSE UI_HEADERS "include/widgets/*.h" "include/mainwindow.h") +set(UI_SOURCES ${UI_SOURCES_CPP} ${UI_HEADERS}) # Versioning if(NOT NUTRA_VERSION) @@ -38,14 +40,10 @@ if(NOT NUTRA_VERSION) endif() add_compile_definitions(NUTRA_VERSION_STRING="${NUTRA_VERSION}") -# Core Library -add_library(nutra_lib STATIC ${SOURCES} ${HEADERS} "resources.qrc") -target_include_directories(nutra_lib PUBLIC ${CMAKE_SOURCE_DIR}/include) -target_link_libraries(nutra_lib PUBLIC Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql) - # Main Executable -add_executable(nutra src/main.cpp) -target_link_libraries(nutra PRIVATE nutra_lib) +add_executable(nutra src/main.cpp ${CORE_SOURCES} ${UI_SOURCES} "resources.qrc") +target_include_directories(nutra PUBLIC ${CMAKE_SOURCE_DIR}/include) +target_link_libraries(nutra PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql) # Testing enable_testing() @@ -58,10 +56,12 @@ add_custom_target(build_tests) foreach(TEST_SOURCE ${TEST_SOURCES}) get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE) - - add_executable(${TEST_NAME} EXCLUDE_FROM_ALL ${TEST_SOURCE}) - target_link_libraries(${TEST_NAME} PRIVATE nutra_lib Qt${QT_VERSION_MAJOR}::Test) - + + # Create independent test executable with only CORE sources + add_executable(${TEST_NAME} EXCLUDE_FROM_ALL ${TEST_SOURCE} ${CORE_SOURCES}) + target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include) + target_link_libraries(${TEST_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Test Qt${QT_VERSION_MAJOR}::Sql) + add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) add_dependencies(build_tests ${TEST_NAME}) endforeach() diff --git a/include/db/reciperepository.h b/include/db/reciperepository.h index df4312d..1beb30a 100644 --- a/include/db/reciperepository.h +++ b/include/db/reciperepository.h @@ -3,6 +3,7 @@ #include #include +#include #include struct RecipeItem { @@ -40,6 +41,11 @@ public: bool removeIngredient(int recipeId, int foodId); bool updateIngredient(int recipeId, int foodId, double amount); std::vector getIngredients(int recipeId); + +private: + void processCsvFile(const QString& filePath, std::map& recipeMap); + int getOrCreateRecipe(const QString& name, const QString& instructions, + std::map& recipeMap); }; #endif // RECIPEREPOSITORY_H diff --git a/src/db/databasemanager.cpp b/src/db/databasemanager.cpp index a957a16..1a87d49 100644 --- a/src/db/databasemanager.cpp +++ b/src/db/databasemanager.cpp @@ -156,6 +156,28 @@ void DatabaseManager::initUserDatabase() { } applySchema(query, schemaPath); } + + // Ensure recipe tables exist + { + query.exec( + "CREATE TABLE IF NOT EXISTS recipe (" + "id integer PRIMARY KEY AUTOINCREMENT," + "uuid text NOT NULL UNIQUE DEFAULT (hex(randomblob(24)))," + "name text NOT NULL," + "instructions text," + "created int DEFAULT (strftime ('%s', 'now'))" + ");"); + + query.exec( + "CREATE TABLE IF NOT EXISTS recipe_ingredient (" + "recipe_id int NOT NULL," + "food_id int NOT NULL," + "amount real NOT NULL," + "msre_id int," + "FOREIGN KEY (recipe_id) REFERENCES recipe (id) ON DELETE CASCADE," + "FOREIGN KEY (msre_id) REFERENCES measure (id) ON UPDATE CASCADE ON DELETE SET NULL" + ");"); + } } void DatabaseManager::applySchema(QSqlQuery& query, const QString& schemaPath) { diff --git a/src/db/reciperepository.cpp b/src/db/reciperepository.cpp index 31fc859..2ba6e32 100644 --- a/src/db/reciperepository.cpp +++ b/src/db/reciperepository.cpp @@ -186,52 +186,57 @@ void RecipeRepository::loadCsvRecipes(const QString& directory) { QDir dir(directory); if (!dir.exists()) return; + // Pre-load all recipes into a map for O(1) lookup + std::vector existingRecipes = getAllRecipes(); + std::map recipeMap; + for (const auto& r : existingRecipes) { + recipeMap[r.name] = r.id; + } + QStringList filters; filters << "*.csv"; QFileInfoList fileList = dir.entryInfoList(filters, QDir::Files); for (const auto& fileInfo : fileList) { - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) continue; + processCsvFile(fileInfo.absoluteFilePath(), recipeMap); + } +} - while (!file.atEnd()) { - QString line = file.readLine().trimmed(); - if (line.isEmpty() || line.startsWith("#")) continue; +void RecipeRepository::processCsvFile(const QString& filePath, std::map& recipeMap) { + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; - QStringList parts = line.split(','); - if (parts.size() < 4) continue; + while (!file.atEnd()) { + QString line = file.readLine().trimmed(); + if (line.isEmpty() || line.startsWith("#")) continue; - QString recipeName = parts[0].trimmed(); - QString instructions = parts[1].trimmed(); - int foodId = parts[2].toInt(); - double amount = parts[3].toDouble(); + QStringList parts = line.split(','); + if (parts.size() < 4) continue; - if (foodId <= 0 || amount <= 0) continue; + QString recipeName = parts[0].trimmed(); + QString instructions = parts[1].trimmed(); + int foodId = parts[2].toInt(); + double amount = parts[3].toDouble(); - // Check if recipe exists or create it - int recipeId = -1; + if (foodId <= 0 || amount <= 0) continue; - // Inefficient check, but works for now. - // Better: Cache existing recipe names -> IDs or add getRecipeByName - auto existingRecipes = getAllRecipes(); - for (const auto& r : existingRecipes) { - if (r.name == recipeName) { - recipeId = r.id; - break; - } - } + int recipeId = getOrCreateRecipe(recipeName, instructions, recipeMap); + if (recipeId != -1) { + addIngredient(recipeId, foodId, amount); + } + } + file.close(); +} - if (recipeId == -1) { - recipeId = createRecipe(recipeName, instructions); - } +int RecipeRepository::getOrCreateRecipe(const QString& name, const QString& instructions, + std::map& recipeMap) { + if (recipeMap.count(name) != 0U) { + return recipeMap[name]; + } - if (recipeId != -1) { - // Check if ingredient exists? - // Or just try insert? database might error on duplicate PK if defined. - // Assuming (recipe_id, food_id) unique constraint? - addIngredient(recipeId, foodId, amount); - } - } - file.close(); + int newId = createRecipe(name, instructions); + if (newId != -1) { + recipeMap[name] = newId; } + return newId; } diff --git a/tests/test_reciperepository.cpp b/tests/test_reciperepository.cpp new file mode 100644 index 0000000..93d2afd --- /dev/null +++ b/tests/test_reciperepository.cpp @@ -0,0 +1,53 @@ +#include +#include +#include + +#include "db/databasemanager.h" +#include "db/reciperepository.h" + +class TestRecipeRepository : public QObject { + Q_OBJECT + +private slots: + void initTestCase() { + // Setup temporary DB and directory + QStandardPaths::setTestModeEnabled(true); + // Ensure user DB is open (in memory or temp file) + // DatabaseManager singleton might need configuration + } + + void testLoadCsv() { + RecipeRepository repo; + + // Create dummy CSV + QString recipeDir = QDir::tempPath() + "/nutra_test_recipes"; + QDir().mkpath(recipeDir); + + QFile file(recipeDir + "/test_recipe.csv"); + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + out << "Unit Test Recipe,Mix it up,1234,200\n"; + file.close(); + } + + repo.loadCsvRecipes(recipeDir); + + // Verify + auto recipes = repo.getAllRecipes(); + bool found = false; + for (const auto& r : recipes) { + if (r.name == "Unit Test Recipe") { + found = true; + break; + } + } + QVERIFY(found); + } + + void cleanupTestCase() { + QDir(QDir::tempPath() + "/nutra_test_recipes").removeRecursively(); + } +}; + +QTEST_MAIN(TestRecipeRepository) +#include "test_reciperepository.moc"