From 2b1b536172d76acdabfc4466007cce321406dd41 Mon Sep 17 00:00:00 2001 From: SimonFJ20 Date: Thu, 6 Mar 2025 15:59:41 +0100 Subject: [PATCH] update build, silent folders, use threads --- backend/Makefile | 13 ++-- backend/compile_flags.txt | 1 + backend/src/controllers.h | 11 ++- backend/src/controllers/products.c | 2 +- backend/src/db.h | 6 +- backend/src/db_sqlite.c | 34 +++++++++- backend/src/http_server.c | 52 ++++++++++++--- backend/src/http_server.h | 1 + backend/src/http_server_internal.h | 2 + backend/src/main.c | 42 ++---------- backend/src/models.c | 103 +++++++++++++++++++++++++++++ backend/src/models.h | 16 +++++ backend/src/models_json.h | 3 + 13 files changed, 230 insertions(+), 56 deletions(-) diff --git a/backend/Makefile b/backend/Makefile index bebaebb..108e0c0 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -9,10 +9,13 @@ # NOTICE that `RELEASE=1` is __after__ `make` # +MAKEFLAGS += -j $(shell nproc) + C_FLAGS = \ -std=c17 \ -Wall -Wextra -Wpedantic -Wconversion \ -pedantic -pedantic-errors \ + -Wno-unused-parameter \ L_FLAGS = -lm -pthread $(shell pkg-config sqlite3 openssl --libs) C_FLAGS = $(shell pkg-config sqlite3 openssl --cflags) @@ -38,13 +41,15 @@ O_FILES = $(patsubst src/%.c,build/%.o,$(C_FILES)) CC = gcc -all: server +TARGET=server -server: $(O_FILES) - $(CC) -o build/$@ $^ $(F_FLAGS) $(OPTIMIZATION) $(L_FLAGS) +all: build/$(TARGET) + +build/$(TARGET): $(O_FILES) + $(CC) -o $@ $^ $(F_FLAGS) $(OPTIMIZATION) $(L_FLAGS) build/%.o: src/%.c $(HEADERS) - mkdir -p $(@D) + @mkdir -p $(dir $@) $(CC) $< -c -o $@ $(C_FLAGS) $(OPTIMIZATION) $(F_FLAGS) clean: diff --git a/backend/compile_flags.txt b/backend/compile_flags.txt index 916642e..482caa8 100644 --- a/backend/compile_flags.txt +++ b/backend/compile_flags.txt @@ -6,4 +6,5 @@ xc -Wconversion -pedantic -pedantic-errors +-Wno-unused-parameter diff --git a/backend/src/controllers.h b/backend/src/controllers.h index d324152..8e917fd 100644 --- a/backend/src/controllers.h +++ b/backend/src/controllers.h @@ -10,6 +10,14 @@ typedef struct { Db* db; } Cx; +void route_get_index(HttpCtx* ctx); +void route_post_set_number(HttpCtx* ctx); +void route_get_not_found(HttpCtx* ctx); + +void route_get_products_all(HttpCtx* ctx); + +void route_post_user_register(HttpCtx* ctx); + #define RESPOND(HTTP_CTX, STATUS, MIME_TYPE, ...) \ { \ HttpCtx* _ctx = (HTTP_CTX); \ @@ -30,4 +38,5 @@ typedef struct { #define RESPOND_JSON(HTTP_CTX, STATUS, ...) \ RESPOND(HTTP_CTX, STATUS, "application/json", __VA_ARGS__) -void route_get_products_all(HttpCtx* ctx); +#define RESPOND_BAD_REQUEST(HTTP_CTX) \ + RESPOND_JSON(HTTP_CTX, 400, "{\"ok\":false}") diff --git a/backend/src/controllers/products.c b/backend/src/controllers/products.c index efa93ea..0a2e88e 100644 --- a/backend/src/controllers/products.c +++ b/backend/src/controllers/products.c @@ -10,7 +10,7 @@ void route_get_products_all(HttpCtx* ctx) ProductVec products; product_vec_construct(&products); - DbRes db_res = db_product_all_fill(cx->db, &products); + DbRes db_res = db_product_all(cx->db, &products); if (db_res != DbRes_Ok) { RESPOND_JSON(ctx, 500, "{\"ok\":false,\"msg\":\"db error\"}"); return; diff --git a/backend/src/db.h b/backend/src/db.h index 1ae2f6a..d07b66d 100644 --- a/backend/src/db.h +++ b/backend/src/db.h @@ -3,6 +3,7 @@ #include "collection.h" #include "models.h" +DEFINE_VEC(int64_t, Ids, ids, 8) DEFINE_VEC(Product, ProductVec, product_vec, 32) typedef enum { @@ -16,5 +17,8 @@ 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); +/// Expects `ids` to be constructed. +DbRes db_users_with_email(Db* db, Ids* ids, const char* email); -DbRes db_product_all_fill(Db* db, ProductVec* vec); +/// Expects `vec` to be constructed. +DbRes db_product_all(Db* db, ProductVec* vec); diff --git a/backend/src/db_sqlite.c b/backend/src/db_sqlite.c index 43bb6e8..ad49ecb 100644 --- a/backend/src/db_sqlite.c +++ b/backend/src/db_sqlite.c @@ -139,7 +139,39 @@ l0_return: return res; } -DbRes db_product_all_fill(Db* db, ProductVec* vec) +DbRes db_users_with_email(Db* db, Ids* ids, const char* email) +{ + static_assert(sizeof(User) == 40, "model has changed"); + + sqlite3* connection; + CONNECT; + DbRes res; + int sqlite_res; + + sqlite3_stmt* stmt; + sqlite_res = sqlite3_prepare_v2( + connection, "SELECT id FROM users WHERE email = ?", -1, &stmt, NULL); + sqlite3_bind_text(stmt, 1, email, -1, NULL); + + while ((sqlite_res = sqlite3_step(stmt)) == SQLITE_ROW) { + int64_t id = GET_INT(0); + ids_push(ids, id); + } + 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; +} + +DbRes db_product_all(Db* db, ProductVec* vec) { sqlite3* connection; CONNECT; diff --git a/backend/src/http_server.c b/backend/src/http_server.c index 057cbb0..4f32719 100644 --- a/backend/src/http_server.c +++ b/backend/src/http_server.c @@ -130,6 +130,11 @@ void* http_ctx_user_ctx(HttpCtx* ctx) return ctx->user_ctx; } +const char* http_ctx_req_path(HttpCtx* ctx) +{ + return ctx->req->path; +} + bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key) { return req_has_header(ctx->req, key); @@ -274,7 +279,7 @@ static inline void worker_handle_request(Worker* worker, Client* client) MAX_HEADER_BUFFER_SIZE); goto l0_return; } - puts((char*)buffer); + // puts((char*)buffer); Req req; size_t body_idx; @@ -309,6 +314,8 @@ static inline void worker_handle_request(Worker* worker, Client* client) header_vec_construct(&handler_ctx.res_headers); + bool been_handled = false; + for (size_t i = 0; i < worker->ctx->server->handlers.size; ++i) { Handler* handler = &worker->ctx->server->handlers.data[i]; if (handler->method != req.method) @@ -316,9 +323,14 @@ static inline void worker_handle_request(Worker* worker, Client* client) if (strcmp(handler->path, req.path) != 0) continue; handler->handler(&handler_ctx); + been_handled = true; break; } + if (!been_handled && worker->ctx->server->not_found_handler != NULL) { + worker->ctx->server->not_found_handler(&handler_ctx); + } + l1_return: header_vec_destroy(&handler_ctx.res_headers); req_destroy(&req); @@ -333,11 +345,11 @@ static inline int parse_header( { StrSplitter splitter = str_split(buf, buf_size, "\r\n"); - StrSlice first = strsplit_next(&splitter); - StrSplitter first_splitter = str_split(first.ptr, first.len, " "); - StrSlice method_str = strsplit_next(&first_splitter); - StrSlice path_str = strsplit_next(&first_splitter); - StrSlice version_str = strsplit_next(&first_splitter); + StrSlice req_line = strsplit_next(&splitter); + StrSplitter req_line_splitter = str_split(req_line.ptr, req_line.len, " "); + StrSlice method_str = strsplit_next(&req_line_splitter); + StrSlice uri_str = strsplit_next(&req_line_splitter); + StrSlice version_str = strsplit_next(&req_line_splitter); if (strncmp(version_str.ptr, "HTTP/1.1", 8) != 0) { fprintf(stderr, "error: unrecognized http version '%.*s'\n", @@ -356,14 +368,32 @@ static inline int parse_header( return -1; } - if (path_str.len >= MAX_PATH_LEN + 1) { + if (uri_str.len >= MAX_PATH_LEN + 1) { fprintf(stderr, "error: path too long\n"); return -1; } - char* path = calloc(MAX_PATH_LEN + 1, sizeof(char)); - strncpy(path, path_str.ptr, path_str.len); - path[path_str.len] = '\0'; + size_t path_len = 0; + while (path_len < uri_str.len && uri_str.ptr[path_len] != '?' + && uri_str.ptr[path_len] != '#') { + path_len += 1; + } + + char* path = calloc(path_len + 1, sizeof(char)); + strncpy(path, uri_str.ptr, path_len); + path[path_len] = '\0'; + + char* query = NULL; + if (path_len < uri_str.len) { + size_t query_len = 0; + while (path_len + query_len < uri_str.len + && uri_str.ptr[path_len + query_len] != '#') { + query_len += 1; + } + query = calloc(query_len + 1, sizeof(char)); + strncpy(query, &uri_str.ptr[path_len], query_len); + query[query_len] = '\0'; + } HeaderVec headers; header_vec_construct(&headers); @@ -402,7 +432,7 @@ static inline int parse_header( header_vec_push(&headers, (Header) { key, value }); } - *req = (Req) { method, path, headers }; + *req = (Req) { method, path, query, headers }; return 0; } diff --git a/backend/src/http_server.h b/backend/src/http_server.h index 4c50fb5..0c16219 100644 --- a/backend/src/http_server.h +++ b/backend/src/http_server.h @@ -29,6 +29,7 @@ void http_server_post( void http_server_set_not_found(HttpServer* server, HttpHandlerFn handler); void* http_ctx_user_ctx(HttpCtx* ctx); +const char* http_ctx_req_path(HttpCtx* ctx); bool http_ctx_req_headers_has(HttpCtx* ctx, const char* key); const char* http_ctx_req_headers_get(HttpCtx* ctx, const char* key); const char* http_ctx_req_body(HttpCtx* ctx); diff --git a/backend/src/http_server_internal.h b/backend/src/http_server_internal.h index 856477b..7620006 100644 --- a/backend/src/http_server_internal.h +++ b/backend/src/http_server_internal.h @@ -47,6 +47,7 @@ static inline void worker_handle_request(Worker* worker, Client* req); #define MAX_HEADER_BUFFER_SIZE 8192 #define MAX_PATH_LEN 128 - 1 +#define MAX_QUERY_LEN 128 - 1 #define MAX_HEADERS_LEN 32 #define MAX_HEADER_KEY_LEN 32 - 1 #define MAX_HEADER_VALUE_LEN 512 - 1 @@ -66,6 +67,7 @@ DEFINE_VEC(Header, HeaderVec, header_vec, 8) typedef struct { Method method; char* path; + char* query; HeaderVec headers; } Req; diff --git a/backend/src/main.c b/backend/src/main.c index 7f52eaf..cad978a 100644 --- a/backend/src/main.c +++ b/backend/src/main.c @@ -8,42 +8,6 @@ #include #include #include - -void route_get_index(HttpCtx* ctx) -{ - Cx* cx = http_ctx_user_ctx(ctx); - - RESPOND_HTML(ctx, 200, - "

Number = %d

", - cx->number); -} - -void route_post_set_number(HttpCtx* ctx) -{ - Cx* cx = http_ctx_user_ctx(ctx); - - const char* body_text = http_ctx_req_body(ctx); - JsonParser parser; - json_parser_construct(&parser, body_text, strlen(body_text)); - JsonValue* body = json_parser_parse(&parser); - json_parser_destroy(&parser); - - if (!json_object_has(body, "value")) { - RESPOND_JSON( - ctx, 200, "{\"ok\": false, \"msg\": \"no 'value' key\"}\r\n"); - goto l0_return; - } - - int64_t value = json_int(json_object_get(body, "value")); - cx->number = (int)value; - - RESPOND_JSON(ctx, 200, "{\"ok\": true}\r\n"); - -l0_return: - json_free(body); -} - HttpServer* server; int main(void) @@ -65,11 +29,15 @@ int main(void) http_server_set_user_ctx(server, &cx); - http_server_get(server, "/products/all", route_get_products_all); + http_server_get(server, "/api/products/all", route_get_products_all); + + http_server_post(server, "/api/users/register", route_post_user_register); http_server_get(server, "/", route_get_index); http_server_post(server, "/set_number", route_post_set_number); + http_server_set_not_found(server, route_get_not_found); + printf("listening at http://127.0.0.1:8080/\n"); http_server_listen(server); diff --git a/backend/src/models.c b/backend/src/models.c index df688c0..81e8c6f 100644 --- a/backend/src/models.c +++ b/backend/src/models.c @@ -1,5 +1,6 @@ #include "models.h" #include "json.h" +#include "models_json.h" #include "str_util.h" #include #include @@ -7,6 +8,8 @@ void user_free(User* m) { + static_assert(sizeof(User) == 40, "model has changed"); + free(m->name); free(m->email); free(m->password_hash); @@ -14,30 +17,57 @@ void user_free(User* m) void coord_free(Coord* m) { + static_assert(sizeof(Coord) == 24, "model has changed"); + (void)m; } void product_free(Product* m) { + static_assert(sizeof(Product) == 40, "model has changed"); + free(m->name); free(m->barcode); } void product_price_free(ProductPrice* m) { + static_assert(sizeof(ProductPrice) == 24, "model has changed"); + (void)m; } void cart_free(Cart* m) { + static_assert(sizeof(Cart) == 16, "model has changed"); + (void)m; } void cart_item_free(CartItem* m) { + static_assert(sizeof(CartItem) == 24, "model has changed"); + (void)m; } +void users_register_req_free(UsersRegisterReq* model) +{ + static_assert(sizeof(UsersRegisterReq) == 24, "model has changed"); + + free(model->name); + free(model->email); + free(model->password); +} + +void auth_login_req_free(AuthLoginReq* model) +{ + static_assert(sizeof(AuthLoginReq) == 16, "model has changed"); + + free(model->email); + free(model->password); +} + char* user_to_json_string(const User* m) { static_assert(sizeof(User) == 40, "model has changed"); @@ -155,6 +185,43 @@ char* cart_item_to_json_string(const CartItem* m) return result; } +char* users_register_req_to_json(const UsersRegisterReq* m) +{ + static_assert(sizeof(UsersRegisterReq) == 24, "model has changed"); + + String string; + string_construct(&string); + string_pushf(&string, + "{" + "\"name\":\"%s\"," + "\"email\":\"%s\"," + "\"password\":\"%s\"" + "}", + m->name, m->email, m->password); + + char* result = string_copy(&string); + string_destroy(&string); + return result; +} + +char* auth_login_req_to_json(const AuthLoginReq* m) +{ + static_assert(sizeof(AuthLoginReq) == 16, "model has changed"); + + String string; + string_construct(&string); + string_pushf(&string, + "{" + "\"email\":\"%s\"," + "\"password\":\"%s\"" + "}", + m->email, m->password); + + char* result = string_copy(&string); + string_destroy(&string); + return result; +} + typedef struct { const char* key; JsonType type; @@ -299,3 +366,39 @@ int cart_item_from_json(CartItem* m, const JsonValue* json) }; return 0; } + +int users_register_req_from_json(UsersRegisterReq* m, const JsonValue* json) +{ + static_assert(sizeof(UsersRegisterReq) == 24, "model has changed"); + + ObjField fields[] = { + { "name", JsonType_String }, + { "email", JsonType_String }, + { "password", JsonType_String }, + }; + if (!obj_conforms(json, fields, sizeof(fields) / sizeof(fields[0]))) + return -1; + *m = (UsersRegisterReq) { + .name = GET_STR("name"), + .email = GET_STR("email"), + .password = GET_STR("password"), + }; + return 0; +} + +int auth_login_req_from_json(AuthLoginReq* m, const JsonValue* json) +{ + static_assert(sizeof(AuthLoginReq) == 16, "model has changed"); + + ObjField fields[] = { + { "email", JsonType_String }, + { "password", JsonType_String }, + }; + if (!obj_conforms(json, fields, sizeof(fields) / sizeof(fields[0]))) + return -1; + *m = (AuthLoginReq) { + .email = GET_STR("email"), + .password = GET_STR("password"), + }; + return 0; +} diff --git a/backend/src/models.h b/backend/src/models.h index d982514..fec6cfe 100644 --- a/backend/src/models.h +++ b/backend/src/models.h @@ -47,3 +47,19 @@ void product_free(Product* model); void product_price_free(ProductPrice* model); void cart_free(Cart* model); void cart_item_free(CartItem* model); + +// + +typedef struct { + char* name; + char* email; + char* password; +} UsersRegisterReq; + +typedef struct { + char* email; + char* password; +} AuthLoginReq; + +void users_register_req_free(UsersRegisterReq* model); +void auth_login_req_free(AuthLoginReq* model); diff --git a/backend/src/models_json.h b/backend/src/models_json.h index e804b62..da34362 100644 --- a/backend/src/models_json.h +++ b/backend/src/models_json.h @@ -11,3 +11,6 @@ DEFINE_MODEL_JSON(Product, product) DEFINE_MODEL_JSON(ProductPrice, product_price) DEFINE_MODEL_JSON(Cart, cart) DEFINE_MODEL_JSON(CartItem, cart_item) + +DEFINE_MODEL_JSON(UsersRegisterReq, users_register_req) +DEFINE_MODEL_JSON(AuthLoginReq, auth_login_req)