diff --git a/backend/prepare.sql b/backend/prepare.sql index b911ea3..552fde1 100644 --- a/backend/prepare.sql +++ b/backend/prepare.sql @@ -1,49 +1,54 @@ CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, + id INT PRIMARY KEY, email TEXT NOT NULL, password_hash TEXT NOT NULL, - balance_dkk_cent INTEGER NOT NULL + balance_dkk_cent INT NOT NULL ); CREATE TABLE IF NOT EXISTS coords ( - id INTEGER PRIMARY KEY, - x INTEGER NOT NULL, - y INTEGER NOT NULL + id INT PRIMARY KEY, + x INT NOT NULL, + y INT NOT NULL ); CREATE TABLE IF NOT EXISTS products ( - id INTEGER PRIMARY KEY, + id INT PRIMARY KEY, name TEXT NOT NULL, - price_dkk_cent INTEGER NOT NULL, + price_dkk_cent INT NOT NULL, description TEXT NOT NULL, - coord INTEGER, + coord INT, barcode TEXT, FOREIGN KEY(coord) REFERENCES coords(id) ); CREATE TABLE IF NOT EXISTS product_prices ( - id INTEGER PRIMARY KEY, - product INTEGER NOT NULL, - price_dkk_cent INTEGER NOT NULL, + id INT PRIMARY KEY, + product INT NOT NULL, + price_dkk_cent INT NOT NULL, FOREIGN KEY(product) REFERENCES products(id) ); CREATE TABLE IF NOT EXISTS carts ( - id INTEGER PRIMARY KEY, - user INTEGER NOT NULL, + id INT PRIMARY KEY, + user INT NOT NULL, FOREIGN KEY(user) REFERENCES users(id) ); CREATE TABLE IF NOT EXISTS cart_items ( - id INTEGER PRIMARY KEY, - cart INTEGER NOT NULL, - amount INTEGER NOT NULL, + id INT PRIMARY KEY, + cart INT NOT NULL, + amount INT NOT NULL, FOREIGN KEY(cart) REFERENCES carts(id) ); + +INSERT OR REPLACE INTO users VALUES(1,'test@email.com','d980840fcb82970ab86656feebdccdd288be0e9b05f14e712b59529a2868fee3d980840fcb82970ab86656feebdccdd288be0e9b05f14e712b59529a2868fee3',10000); + +INSERT OR REPLACE INTO products VALUES(1,'Letmælk',1195,'Mælk fra ko',NULL,NULL); +INSERT OR REPLACE INTO products VALUES(2,'Smør',2000,'Smør fra mejeri',NULL,NULL); diff --git a/backend/src/controllers.h b/backend/src/controllers.h new file mode 100644 index 0000000..e7f294b --- /dev/null +++ b/backend/src/controllers.h @@ -0,0 +1,30 @@ +#pragma once + +#include "db.h" +#include +#include + +typedef struct { + int number; + Db* db; +} Cx; + +#define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \ + { \ + HttpCtx* _ctx = (HTTP_CTX); \ + char _body[512]; \ + snprintf(_body, 512 - 1, __VA_ARGS__); \ + \ + char content_length[24] = { 0 }; \ + snprintf(content_length, 24 - 1, "%ld", strlen(_body)); \ + \ + http_ctx_res_headers_set(_ctx, "Content-Type", MIME_TYPE); \ + http_ctx_res_headers_set(_ctx, "Content-Length", content_length); \ + \ + http_ctx_respond(_ctx, (STATUS), _body); \ + } + +#define RESPOND_HTML(HTTP_CTX, STATUS, ...) \ + RESPOND(HTTP_CTX, STATUS, "text/html", __VA_ARGS__) +#define RESPOND_JSON(HTTP_CTX, STATUS, ...) \ + RESPOND(HTTP_CTX, STATUS, "application/json", __VA_ARGS__) diff --git a/backend/src/controllers/products.c b/backend/src/controllers/products.c new file mode 100644 index 0000000..f872425 --- /dev/null +++ b/backend/src/controllers/products.c @@ -0,0 +1,37 @@ +#include "products.h" +#include "../controllers.h" +#include "../http_server.h" +#include "../models_json.h" +#include "../str_util.h" + +void route_get_products_all(HttpCtx* ctx) +{ + Cx* cx = http_ctx_user_ctx(ctx); + + ProductVec products; + product_vec_construct(&products); + + DbRes db_res = db_product_all_fill(cx->db, &products); + if (db_res != DbRes_Ok) { + RESPOND_JSON(ctx, 500, "{\"ok\":false,\"msg\":\"db error\"}"); + return; + } + + String res; + string_construct(&res); + + string_push_str(&res, "{\"ok\":true,\"products\":["); + for (size_t i = 0; i < products.size; ++i) { + if (i != 0) { + string_push_str(&res, ","); + } + char* json = product_to_json_string(&products.data[i]); + string_push_str(&res, json); + free(json); + } + string_push_str(&res, "]}"); + + product_vec_destroy(&products); + RESPOND_JSON(ctx, 200, "%s", res.data); + string_destroy(&res); +} diff --git a/backend/src/controllers/products.h b/backend/src/controllers/products.h new file mode 100644 index 0000000..3e0c999 --- /dev/null +++ b/backend/src/controllers/products.h @@ -0,0 +1,6 @@ +#pragma once + +#include "../controllers.h" +#include "../http_server.h" + +void route_get_products_all(HttpCtx* ctx); diff --git a/backend/src/db.h b/backend/src/db.h new file mode 100644 index 0000000..1ae2f6a --- /dev/null +++ b/backend/src/db.h @@ -0,0 +1,20 @@ +#pragma once + +#include "collection.h" +#include "models.h" + +DEFINE_VEC(Product, ProductVec, product_vec, 32) + +typedef enum { + DbRes_Ok, + DbRes_NotFound, + DbRes_Error, +} DbRes; + +typedef struct Db Db; + +/// `user.id` field is ignored. +DbRes db_user_insert(Db* db, User* user); +DbRes db_user_from_id(Db* db, User* user, int64_t id); + +DbRes db_product_all_fill(Db* db, ProductVec* vec); diff --git a/backend/src/db_sqlite.c b/backend/src/db_sqlite.c new file mode 100644 index 0000000..7ea9755 --- /dev/null +++ b/backend/src/db_sqlite.c @@ -0,0 +1,174 @@ +#include "db_sqlite.h" +#include "db.h" +#include "str_util.h" +#include +#include + +static inline char* get_str_safe(sqlite3_stmt* stmt, int col) +{ + const char* val = (const char*)sqlite3_column_text(stmt, col); + if (!val) + return str_dup("NULL"); + return str_dup((const char*)val); +} + +#define GET_INT(COL) sqlite3_column_int64(stmt, COL) +#define GET_STR(COL) get_str_safe(stmt, COL) + +Db* db_sqlite_new(void) +{ + Db* db = malloc(sizeof(Db)); + + sqlite3* connection; + int res = sqlite3_open("database.db", &connection); + if (res != SQLITE_OK) { + fprintf(stderr, "error: could not open sqlite 'database.db'\n"); + return NULL; + } + sqlite3_close(connection); + + return db; +} + +void db_sqlite_free(Db* db) +{ + // sqlite3_close(db->connection); + free(db); +} + +static inline DbRes connect(sqlite3** connection) +{ + int res = sqlite3_open("database.db", connection); + if (res != SQLITE_OK) { + fprintf(stderr, "error: could not open sqlite 'database.db'\n %s\n", + sqlite3_errmsg(*connection)); + return DbRes_Error; + } + return DbRes_Ok; +} + +static inline void disconnect(sqlite3* connection) +{ + sqlite3_close(connection); +} + +#define CONNECT \ + { \ + if (connect(&connection) != DbRes_Ok) { \ + return DbRes_Error; \ + } \ + } + +#define DISCONNECT \ + { \ + disconnect(connection); \ + } + +DbRes db_user_insert(Db* db, User* user) +{ + sqlite3* connection; + CONNECT; + DbRes res; + + sqlite3_stmt* stmt; + sqlite3_prepare_v2(connection, + "INSERT INTO users (email, password_hash, balance_dkk_cent) " + "VALUES (?, ?, ?)", + -1, &stmt, NULL); + + sqlite3_bind_text(stmt, 1, user->email, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, user->password_hash, -1, SQLITE_STATIC); + sqlite3_bind_int64(stmt, 3, user->balance_dkk_cent); + + int step_res = sqlite3_step(stmt); + if (step_res != SQLITE_DONE) { + fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection)); + res = DbRes_Error; + goto l0_return; + } + + res = DbRes_Ok; +l0_return: + if (stmt) + sqlite3_finalize(stmt); + DISCONNECT; + return res; +} + +DbRes db_user_from_id(Db* db, User* user, int64_t id) +{ + sqlite3* connection; + CONNECT; + DbRes res; + + sqlite3_stmt* stmt; + sqlite3_prepare_v2(connection, + "SELECT id, email, password_hash, balance_dkk_cent" + "FROM users WHERE id = ?", + -1, &stmt, NULL); + sqlite3_bind_int64(stmt, 1, id); + + int step_res = sqlite3_step(stmt); + if (step_res == SQLITE_DONE) { + res = DbRes_NotFound; + goto l0_return; + } else if (step_res != SQLITE_ROW) { + fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection)); + res = DbRes_Error; + goto l0_return; + } + *user = (User) { + .id = GET_INT(0), + .email = GET_STR(1), + .password_hash = GET_STR(2), + .balance_dkk_cent = GET_INT(3), + }; + + res = DbRes_Ok; +l0_return: + if (stmt) + sqlite3_finalize(stmt); + DISCONNECT; + return res; +} + +DbRes db_product_all_fill(Db* db, ProductVec* vec) +{ + sqlite3* connection; + CONNECT; + DbRes res; + int sqlite_res; + + sqlite3_stmt* stmt; + sqlite_res = sqlite3_prepare_v2(connection, + "SELECT id, name, price_dkk_cent, coord, barcode FROM products", -1, + &stmt, NULL); + if (sqlite_res != SQLITE_OK) { + fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection)); + res = DbRes_Error; + goto l0_return; + } + + while ((sqlite_res = sqlite3_step(stmt)) == SQLITE_ROW) { + Product product = { + .id = GET_INT(0), + .name = GET_STR(1), + .price_dkk_cent = GET_INT(2), + .coord_id = GET_INT(3), + .barcode = GET_STR(4), + }; + product_vec_push(vec, product); + } + if (sqlite_res != SQLITE_DONE) { + fprintf(stderr, "error: %s\n", sqlite3_errmsg(connection)); + res = DbRes_Error; + goto l0_return; + } + + res = DbRes_Ok; +l0_return: + if (stmt) + sqlite3_finalize(stmt); + DISCONNECT; + return res; +} diff --git a/backend/src/db_sqlite.h b/backend/src/db_sqlite.h new file mode 100644 index 0000000..e2ebb12 --- /dev/null +++ b/backend/src/db_sqlite.h @@ -0,0 +1,11 @@ +#pragma once + +#include "db.h" +#include + +struct Db { + int empty; +}; + +Db* db_sqlite_new(void); +void db_sqlite_free(Db* db); diff --git a/backend/src/main.c b/backend/src/main.c index 132b191..09eca45 100644 --- a/backend/src/main.c +++ b/backend/src/main.c @@ -1,3 +1,6 @@ +#include "controllers.h" +#include "controllers/products.h" +#include "db_sqlite.h" #include "http_server.h" #include "json.h" #include "models.h" @@ -7,30 +10,6 @@ #include #include -typedef struct { - int number; -} Cx; - -#define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \ - { \ - HttpCtx* _ctx = (HTTP_CTX); \ - char _body[512]; \ - snprintf(_body, 512 - 1, __VA_ARGS__); \ - \ - char content_length[24] = { 0 }; \ - snprintf(content_length, 24 - 1, "%ld", strlen(_body)); \ - \ - http_ctx_res_headers_set(_ctx, "Content-Type", MIME_TYPE); \ - http_ctx_res_headers_set(_ctx, "Content-Length", content_length); \ - \ - http_ctx_respond(_ctx, (STATUS), _body); \ - } - -#define RESPOND_HTML(HTTP_CTX, STATUS, ...) \ - RESPOND(HTTP_CTX, STATUS, "text/html", __VA_ARGS__) -#define RESPOND_JSON(HTTP_CTX, STATUS, ...) \ - RESPOND(HTTP_CTX, STATUS, "application/json", __VA_ARGS__) - void route_get_index(HttpCtx* ctx) { Cx* cx = http_ctx_user_ctx(ctx); @@ -66,76 +45,17 @@ l0_return: json_free(body); } -static inline void insert_test_user(sqlite3* db) -{ - sqlite3_stmt* stmt; - sqlite3_prepare_v2(db, - "INSERT INTO users (email, password_hash, balance_dkk_cent) " - "VALUES (?, ?, ?)", - -1, &stmt, NULL); - - char email[] = "testuser@email.com"; - char password[] = "1234"; - StrHash password_hash = str_hash(password); - char* password_hash_str = str_hash_to_string(password_hash); - - sqlite3_bind_text(stmt, 1, email, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 2, password_hash_str, -1, SQLITE_STATIC); - sqlite3_bind_int64(stmt, 3, 123); - - int res = sqlite3_step(stmt); - if (res != SQLITE_DONE) { - fprintf(stderr, "error: could not insert test user: %s\n", - sqlite3_errmsg(db)); - } - - sqlite3_finalize(stmt); -} - HttpServer* server; int main(void) { + Db* db = db_sqlite_new(); - User user = { - .id = 12, - .email = str_dup("test@mail.dk"), - .password_hash = str_dup("hawd"), - .balance_dkk_cent = 321, + Cx cx = { + .number = 1, + .db = db, }; - char* str = user_to_json_string(&user); - printf("user = '%s'\n", str); - - User user2; - - JsonValue* json = json_parse(str, strlen(str)); - - user_from_json(&user2, json); - - char* str2 = user_to_json_string(&user2); - printf("user2 = '%s'\n", str2); - - user_free(&user); - user_free(&user2); - - json_free(json); - free(str); - free(str2); - - return 0; - - sqlite3* db; - int res = sqlite3_open("database.db", &db); - if (res != SQLITE_OK) { - fprintf(stderr, "error: could not open sqlite 'database.db'\n"); - return -1; - } - - insert_test_user(db); - - Cx cx = { .number = 1 }; - server = http_server_new((HttpServerOpts) { .port = 8080, .workers_amount = 8, @@ -145,6 +65,9 @@ int main(void) } http_server_set_user_ctx(server, &cx); + + http_server_get(server, "/products/all", route_get_products_all); + http_server_get(server, "/", route_get_index); http_server_post(server, "/set_number", route_post_set_number); @@ -152,5 +75,5 @@ int main(void) http_server_listen(server); http_server_free(server); - sqlite3_close(db); + db_sqlite_free(db); } diff --git a/backend/src/str_util.c b/backend/src/str_util.c index b4c8965..126b099 100644 --- a/backend/src/str_util.c +++ b/backend/src/str_util.c @@ -46,6 +46,7 @@ void string_push_str(String* string, const char* str) for (size_t i = 0; i < strlen(str); ++i) { string_push(string, str[i]); } + string->data[string->size] = '\0'; } void string_push_fmt_va(String* string, const char* fmt, ...)