mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
product editor
This commit is contained in:
parent
f36505a38e
commit
e494277978
@ -3,8 +3,107 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="product_editor.js" type="module" defer></script>
|
<script src="product_editor.js" type="module" defer></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 1600px;
|
||||||
|
}
|
||||||
|
#product-list {
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
#product-list th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #eee;
|
||||||
|
border-right: 1px solid lightgray;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
#product-list th:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
#product-list td {
|
||||||
|
border-top: 1px solid black;
|
||||||
|
border-right: 1px solid lightgray;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
#product-list td:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
#product-list tr:nth-child(2n) td {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
background-color: #F9F9F9;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
legend {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
#editor table {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
#editor td {
|
||||||
|
padding: 0.25rem;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
#editor input, #editor textarea {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Product editor</h1>
|
<h2>Products</h2>
|
||||||
|
<table id="product-list">
|
||||||
|
</table>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Editor</legend>
|
||||||
|
|
||||||
|
<form id="editor">
|
||||||
|
<center>
|
||||||
|
<button id="load">Load</button>
|
||||||
|
<button id="save">Save</button>
|
||||||
|
<button id="new">New</button>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><label for="product-id">id: </label></td>
|
||||||
|
<td>
|
||||||
|
<input id="product-id" type="text">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="product-name">name: </label></td>
|
||||||
|
<td><input id="product-name" type="text"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="product-price">price (dkk): </label></td>
|
||||||
|
<td><input id="product-price" type="number" step="0.01"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="product-description">description: </label></td>
|
||||||
|
<td><textarea id="product-description"></textarea></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="product-coord">coord_id: </label></td>
|
||||||
|
<td><input id="product-coord" type="text"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="product-barcode">barcode: </label></td>
|
||||||
|
<td><input id="product-barcode" type="text"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,3 +1,135 @@
|
|||||||
|
|
||||||
console.log("hello world")
|
const productList = document.querySelector("#product-list");
|
||||||
|
const editor = {
|
||||||
|
form: document.querySelector("#editor"),
|
||||||
|
loadButton: document.querySelector("#editor #load"),
|
||||||
|
saveButton: document.querySelector("#editor #save"),
|
||||||
|
newButton: document.querySelector("#editor #new"),
|
||||||
|
idInput: document.querySelector("#editor #product-id"),
|
||||||
|
nameInput: document.querySelector("#editor #product-name"),
|
||||||
|
priceInput: document.querySelector("#editor #product-price"),
|
||||||
|
descriptionTextarea: document.querySelector("#editor #product-description"),
|
||||||
|
coordInput: document.querySelector("#editor #product-coord"),
|
||||||
|
barcodeInput: document.querySelector("#editor #product-barcode"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let products = [];
|
||||||
|
|
||||||
|
let selectedProductId = null;
|
||||||
|
|
||||||
|
function selectProduct(product) {
|
||||||
|
selectedProductId = product.id;
|
||||||
|
|
||||||
|
editor.idInput.value = product.id.toString();
|
||||||
|
editor.nameInput.value = product.name;
|
||||||
|
editor.priceInput.value = product.price_dkk_cent / 100;
|
||||||
|
editor.descriptionTextarea.value = product.description;
|
||||||
|
editor.coordInput.value = product.coord_id.toString();
|
||||||
|
editor.barcodeInput.value = product.barcode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProduct() {
|
||||||
|
selectedProductId = parseInt(editor.idInput.value);
|
||||||
|
|
||||||
|
const product = products.find(product => product.id === selectedProductId);
|
||||||
|
if (!product){
|
||||||
|
alert(`no product with id ${selectedProductId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectProduct(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
function productFromForm() {
|
||||||
|
return {
|
||||||
|
id: parseInt(editor.idInput.value),
|
||||||
|
name: editor.nameInput.value,
|
||||||
|
description: editor.descriptionTextarea.value,
|
||||||
|
price_dkk_cent: Math.floor(parseFloat(editor.priceInput.value) * 100),
|
||||||
|
coord_id: parseInt(editor.coordInput.value),
|
||||||
|
barcode: editor.barcodeInput.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveProduct() {
|
||||||
|
const product = productFromForm();
|
||||||
|
await fetch("/api/products/update", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {"Content-Type": "application/json"},
|
||||||
|
body: JSON.stringify(product),
|
||||||
|
}).then(res => res.json());
|
||||||
|
|
||||||
|
await updateProductList();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newProduct() {
|
||||||
|
const product = productFromForm();
|
||||||
|
await fetch("/api/products/create", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {"Content-Type": "application/json"},
|
||||||
|
body: JSON.stringify(product),
|
||||||
|
}).then(res => res.json());
|
||||||
|
|
||||||
|
await updateProductList();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateProductList() {
|
||||||
|
const res = await fetch("/api/products/all")
|
||||||
|
.then(res => res.json());
|
||||||
|
|
||||||
|
products = res.products;
|
||||||
|
|
||||||
|
productList.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<th>id</th>
|
||||||
|
<th>name</th>
|
||||||
|
<th>price_dkk_cent</th>
|
||||||
|
<th>description</th>
|
||||||
|
<th>coord_id</th>
|
||||||
|
<th>barcode</th>
|
||||||
|
<th> </th>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
productList.innerHTML += products
|
||||||
|
.map(product => `
|
||||||
|
<tr>
|
||||||
|
<td><code>${product.id}</code></td>
|
||||||
|
<td><strong>${product.name}</strong></td>
|
||||||
|
<td>${product.price_dkk_cent / 100} dkk</td>
|
||||||
|
<td>${product.description}</td>
|
||||||
|
<td><code>${product.coord_id}</code></td>
|
||||||
|
<td><code>${product.barcode}</code></td>
|
||||||
|
<td><button id="product-${product.id}-edit">Edit</button></td>
|
||||||
|
</tr>
|
||||||
|
`)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
for (const product of products) {
|
||||||
|
document
|
||||||
|
.querySelector(`#product-${product.id}-edit`)
|
||||||
|
.addEventListener("click", () => {
|
||||||
|
selectProduct(product);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProductList();
|
||||||
|
|
||||||
|
editor.form
|
||||||
|
.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
editor.loadButton
|
||||||
|
.addEventListener("click", (e) => {
|
||||||
|
loadProduct();
|
||||||
|
});
|
||||||
|
editor.saveButton
|
||||||
|
.addEventListener("click", (e) => {
|
||||||
|
saveProduct();
|
||||||
|
});
|
||||||
|
editor.newButton
|
||||||
|
.addEventListener("click", (e) => {
|
||||||
|
newProduct();
|
||||||
|
});
|
||||||
|
|
||||||
|
@ -36,6 +36,9 @@ void route_post_set_number(HttpCtx* ctx);
|
|||||||
void route_get_not_found(HttpCtx* ctx);
|
void route_get_not_found(HttpCtx* ctx);
|
||||||
|
|
||||||
void route_get_products_all(HttpCtx* ctx);
|
void route_get_products_all(HttpCtx* ctx);
|
||||||
|
void route_post_products_create(HttpCtx* ctx);
|
||||||
|
void route_post_products_update(HttpCtx* ctx);
|
||||||
|
|
||||||
void route_get_product_editor_html(HttpCtx* ctx);
|
void route_get_product_editor_html(HttpCtx* ctx);
|
||||||
void route_get_product_editor_js(HttpCtx* ctx);
|
void route_get_product_editor_js(HttpCtx* ctx);
|
||||||
|
|
||||||
|
@ -36,6 +36,83 @@ void route_get_products_all(HttpCtx* ctx)
|
|||||||
string_destroy(&res);
|
string_destroy(&res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void route_post_products_create(HttpCtx* ctx)
|
||||||
|
{
|
||||||
|
Cx* cx = http_ctx_user_ctx(ctx);
|
||||||
|
|
||||||
|
const char* body_str = http_ctx_req_body(ctx);
|
||||||
|
JsonValue* body_json = json_parse(body_str, strlen(body_str));
|
||||||
|
if (!body_json) {
|
||||||
|
RESPOND_BAD_REQUEST(ctx, "bad request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductsCreateReq req;
|
||||||
|
int parse_result = products_create_req_from_json(&req, body_json);
|
||||||
|
json_free(body_json);
|
||||||
|
if (parse_result != 0) {
|
||||||
|
RESPOND_BAD_REQUEST(ctx, "bad request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Product product = {
|
||||||
|
.id = 0,
|
||||||
|
.name = str_dup(req.name),
|
||||||
|
.price_dkk_cent = req.price_dkk_cent,
|
||||||
|
.description = str_dup(req.description),
|
||||||
|
.coord_id = req.coord_id,
|
||||||
|
.barcode = str_dup(req.barcode),
|
||||||
|
};
|
||||||
|
products_create_req_destroy(&req);
|
||||||
|
|
||||||
|
DbRes db_res = db_product_insert(cx->db, &product);
|
||||||
|
if (db_res != DbRes_Ok) {
|
||||||
|
RESPOND_SERVER_ERROR(ctx);
|
||||||
|
goto l0_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RESPOND_JSON(ctx, 200, "{\"ok\":true}");
|
||||||
|
|
||||||
|
l0_return:
|
||||||
|
product_destroy(&product);
|
||||||
|
}
|
||||||
|
|
||||||
|
void route_post_products_update(HttpCtx* ctx)
|
||||||
|
{
|
||||||
|
Cx* cx = http_ctx_user_ctx(ctx);
|
||||||
|
|
||||||
|
const char* body_str = http_ctx_req_body(ctx);
|
||||||
|
printf("body_str = '%s'\n", body_str);
|
||||||
|
|
||||||
|
JsonValue* body_json = json_parse(body_str, strlen(body_str));
|
||||||
|
printf("body_json = %p\n", (void*)body_json);
|
||||||
|
|
||||||
|
if (!body_json) {
|
||||||
|
RESPOND_BAD_REQUEST(ctx, "bad request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Product product;
|
||||||
|
int parse_result = product_from_json(&product, body_json);
|
||||||
|
printf("parse_result = %d\n", parse_result);
|
||||||
|
json_free(body_json);
|
||||||
|
if (parse_result != 0) {
|
||||||
|
RESPOND_BAD_REQUEST(ctx, "bad request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbRes db_res = db_product_update(cx->db, &product);
|
||||||
|
if (db_res != DbRes_Ok) {
|
||||||
|
RESPOND_SERVER_ERROR(ctx);
|
||||||
|
goto l0_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RESPOND_JSON(ctx, 200, "{\"ok\":true}");
|
||||||
|
|
||||||
|
l0_return:
|
||||||
|
product_destroy(&product);
|
||||||
|
}
|
||||||
|
|
||||||
static inline int read_and_send_file(HttpCtx* ctx,
|
static inline int read_and_send_file(HttpCtx* ctx,
|
||||||
const char* filepath,
|
const char* filepath,
|
||||||
size_t max_file_size,
|
size_t max_file_size,
|
||||||
|
@ -30,6 +30,12 @@ DbRes db_user_with_email_exists(Db* db, bool* exists, const char* email);
|
|||||||
/// `user` is an out parameter.
|
/// `user` is an out parameter.
|
||||||
DbRes db_user_with_email(Db* db, User* user, const char* email);
|
DbRes db_user_with_email(Db* db, User* user, const char* email);
|
||||||
|
|
||||||
|
/// `product.id` are ignored.
|
||||||
|
DbRes db_product_insert(Db* db, const Product* product);
|
||||||
|
|
||||||
|
/// Uses `user.id` to find model.
|
||||||
|
DbRes db_product_update(Db* db, const Product* product);
|
||||||
|
|
||||||
/// `product` is an out parameter.
|
/// `product` is an out parameter.
|
||||||
DbRes db_product_with_id(Db* db, Product* product, int64_t id);
|
DbRes db_product_with_id(Db* db, Product* product, int64_t id);
|
||||||
|
|
||||||
|
@ -285,7 +285,97 @@ l0_return:
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `product` is an out parameter.
|
DbRes db_product_insert(Db* db, const Product* product)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(Product) == 48, "model has changed");
|
||||||
|
|
||||||
|
sqlite3* connection;
|
||||||
|
CONNECT;
|
||||||
|
DbRes res;
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
int prepare_res = sqlite3_prepare_v2(connection,
|
||||||
|
"INSERT INTO products"
|
||||||
|
" (name, price_dkk_cent, description, coord, barcode)"
|
||||||
|
" VALUES (?, ?, ?, ?, ?)",
|
||||||
|
-1,
|
||||||
|
&stmt,
|
||||||
|
NULL);
|
||||||
|
if (prepare_res != SQLITE_OK) {
|
||||||
|
REPORT_SQLITE3_ERROR();
|
||||||
|
res = DbRes_Error;
|
||||||
|
goto l0_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, product->name, -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_int64(stmt, 2, product->price_dkk_cent);
|
||||||
|
sqlite3_bind_text(stmt, 3, product->description, -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_int64(stmt, 4, product->coord_id);
|
||||||
|
sqlite3_bind_text(stmt, 5, product->barcode, -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
int step_res = sqlite3_step(stmt);
|
||||||
|
if (step_res != SQLITE_DONE) {
|
||||||
|
REPORT_SQLITE3_ERROR();
|
||||||
|
res = DbRes_Error;
|
||||||
|
goto l0_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = DbRes_Ok;
|
||||||
|
l0_return:
|
||||||
|
if (stmt)
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
DISCONNECT;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
DbRes db_product_update(Db* db, const Product* product)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(Product) == 48, "model has changed");
|
||||||
|
|
||||||
|
sqlite3* connection;
|
||||||
|
CONNECT;
|
||||||
|
DbRes res;
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
int prepare_res = sqlite3_prepare_v2(connection,
|
||||||
|
"UPDATE products SET"
|
||||||
|
" name = ?,"
|
||||||
|
" price_dkk_cent = ?,"
|
||||||
|
" description = ?,"
|
||||||
|
" coord = ?,"
|
||||||
|
" barcode = ?"
|
||||||
|
" WHERE id = ?",
|
||||||
|
-1,
|
||||||
|
&stmt,
|
||||||
|
NULL);
|
||||||
|
if (prepare_res != SQLITE_OK) {
|
||||||
|
REPORT_SQLITE3_ERROR();
|
||||||
|
res = DbRes_Error;
|
||||||
|
goto l0_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, product->name, -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_int64(stmt, 2, product->price_dkk_cent);
|
||||||
|
sqlite3_bind_text(stmt, 3, product->description, -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_int64(stmt, 4, product->coord_id);
|
||||||
|
sqlite3_bind_text(stmt, 5, product->barcode, -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_int64(stmt, 6, product->id);
|
||||||
|
|
||||||
|
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_product_with_id(Db* db, Product* product, int64_t id)
|
DbRes db_product_with_id(Db* db, Product* product, int64_t id)
|
||||||
{
|
{
|
||||||
static_assert(sizeof(Product) == 48, "model has changed");
|
static_assert(sizeof(Product) == 48, "model has changed");
|
||||||
|
@ -148,6 +148,23 @@ void json_parser_destroy(JsonParser* p)
|
|||||||
(void)p;
|
(void)p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void free_unused_arr(Arr* arr)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < arr->size; ++i) {
|
||||||
|
json_free(arr->data[i]);
|
||||||
|
}
|
||||||
|
arr_destroy(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void free_unused_obj(Obj* obj)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < obj->size; ++i) {
|
||||||
|
free(obj->data[i].key);
|
||||||
|
json_free(obj->data[i].val);
|
||||||
|
}
|
||||||
|
obj_destroy(obj);
|
||||||
|
}
|
||||||
|
|
||||||
JsonValue* json_parser_parse(JsonParser* p)
|
JsonValue* json_parser_parse(JsonParser* p)
|
||||||
{
|
{
|
||||||
switch (p->curr_tok) {
|
switch (p->curr_tok) {
|
||||||
@ -188,23 +205,29 @@ JsonValue* json_parser_parse(JsonParser* p)
|
|||||||
arr_construct(&arr);
|
arr_construct(&arr);
|
||||||
{
|
{
|
||||||
JsonValue* value = json_parser_parse(p);
|
JsonValue* value = json_parser_parse(p);
|
||||||
if (!value)
|
if (!value) {
|
||||||
|
free_unused_arr(&arr);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
arr_push(&arr, value);
|
arr_push(&arr, value);
|
||||||
}
|
}
|
||||||
while (p->curr_tok != TOK_EOF && p->curr_tok != ']') {
|
while (p->curr_tok != TOK_EOF && p->curr_tok != ']') {
|
||||||
if (p->curr_tok != ',') {
|
if (p->curr_tok != ',') {
|
||||||
fprintf(stderr, "error: json: expected ',' in array\n");
|
fprintf(stderr, "error: json: expected ',' in array\n");
|
||||||
|
free_unused_arr(&arr);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
lex(p);
|
lex(p);
|
||||||
JsonValue* value = json_parser_parse(p);
|
JsonValue* value = json_parser_parse(p);
|
||||||
if (!value)
|
if (!value) {
|
||||||
|
free_unused_arr(&arr);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
arr_push(&arr, value);
|
arr_push(&arr, value);
|
||||||
}
|
}
|
||||||
if (p->curr_tok != ']') {
|
if (p->curr_tok != ']') {
|
||||||
fprintf(stderr, "error: json: expected ']' after array\n");
|
fprintf(stderr, "error: json: expected ']' after array\n");
|
||||||
|
free_unused_arr(&arr);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
lex(p);
|
lex(p);
|
||||||
@ -218,52 +241,62 @@ JsonValue* json_parser_parse(JsonParser* p)
|
|||||||
{
|
{
|
||||||
if (p->curr_tok != '"') {
|
if (p->curr_tok != '"') {
|
||||||
fprintf(stderr, "error: json: expected '\"' in kv\n");
|
fprintf(stderr, "error: json: expected '\"' in kv\n");
|
||||||
|
free_unused_obj(&obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
char* key = p->curr_val;
|
char* key = p->curr_val;
|
||||||
lex(p);
|
lex(p);
|
||||||
if (p->curr_tok != ':') {
|
if (p->curr_tok != ':') {
|
||||||
fprintf(stderr, "error: json: expected ':' in kv\n");
|
fprintf(stderr, "error: json: expected ':' in kv\n");
|
||||||
|
free_unused_obj(&obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
lex(p);
|
lex(p);
|
||||||
JsonValue* value = json_parser_parse(p);
|
JsonValue* value = json_parser_parse(p);
|
||||||
if (!value)
|
if (!value) {
|
||||||
|
free_unused_obj(&obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
obj_push(&obj, (KV) { key, value });
|
obj_push(&obj, (KV) { key, value });
|
||||||
}
|
}
|
||||||
while (p->curr_tok != TOK_EOF && p->curr_tok != '}') {
|
while (p->curr_tok != TOK_EOF && p->curr_tok != '}') {
|
||||||
if (p->curr_tok != ',') {
|
if (p->curr_tok != ',') {
|
||||||
fprintf(stderr, "error: json: expected ',' in object\n");
|
fprintf(stderr, "error: json: expected ',' in object\n");
|
||||||
|
free_unused_obj(&obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
lex(p);
|
lex(p);
|
||||||
|
|
||||||
if (p->curr_tok != '"') {
|
if (p->curr_tok != '"') {
|
||||||
fprintf(stderr, "error: json: expected '\"' in kv\n");
|
fprintf(stderr, "error: json: expected '\"' in kv\n");
|
||||||
|
free_unused_obj(&obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
char* key = p->curr_val;
|
char* key = p->curr_val;
|
||||||
lex(p);
|
lex(p);
|
||||||
if (p->curr_tok != ':') {
|
if (p->curr_tok != ':') {
|
||||||
fprintf(stderr, "error: json: expected ':' in kv\n");
|
fprintf(stderr, "error: json: expected ':' in kv\n");
|
||||||
|
free_unused_obj(&obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
lex(p);
|
lex(p);
|
||||||
JsonValue* value = json_parser_parse(p);
|
JsonValue* value = json_parser_parse(p);
|
||||||
if (!value)
|
if (!value) {
|
||||||
|
free_unused_obj(&obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
obj_push(&obj, (KV) { key, value });
|
obj_push(&obj, (KV) { key, value });
|
||||||
}
|
}
|
||||||
if (p->curr_tok != '}') {
|
if (p->curr_tok != '}') {
|
||||||
fprintf(stderr, "error: json: expected '}' after object\n");
|
fprintf(stderr, "error: json: expected '}' after object\n");
|
||||||
|
free_unused_obj(&obj);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
lex(p);
|
lex(p);
|
||||||
return alloc((JsonValue) { .type = JsonType_Object, .obj_val = obj });
|
return alloc((JsonValue) { .type = JsonType_Object, .obj_val = obj });
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "error: json: unexpeted tok\n");
|
fprintf(stderr, "error: json: unexpeted token\n");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,6 +333,7 @@ static inline void lex(JsonParser* p)
|
|||||||
case '0':
|
case '0':
|
||||||
lstep(p);
|
lstep(p);
|
||||||
p->curr_tok = TOK_NUMBER;
|
p->curr_tok = TOK_NUMBER;
|
||||||
|
p->curr_val = str_dup("0");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((p->ch >= '1' && p->ch <= '9') || p->ch == '.') {
|
if ((p->ch >= '1' && p->ch <= '9') || p->ch == '.') {
|
||||||
@ -315,14 +349,36 @@ static inline void lex(JsonParser* p)
|
|||||||
string_push(&value, p->ch);
|
string_push(&value, p->ch);
|
||||||
lstep(p);
|
lstep(p);
|
||||||
}
|
}
|
||||||
// should be interned
|
|
||||||
char* copy = string_copy(&value);
|
char* copy = string_copy(&value);
|
||||||
|
|
||||||
string_destroy(&value);
|
string_destroy(&value);
|
||||||
|
|
||||||
p->curr_tok = TOK_NUMBER;
|
p->curr_tok = TOK_NUMBER;
|
||||||
p->curr_val = copy;
|
p->curr_val = copy;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ((p->ch >= 'a' && p->ch <= 'z') || (p->ch >= 'A' && p->ch <= 'Z')) {
|
||||||
|
String value;
|
||||||
|
string_construct(&value);
|
||||||
|
while (p->i < p->text_len
|
||||||
|
&& ((p->ch >= 'a' && p->ch <= 'z')
|
||||||
|
|| (p->ch >= 'A' && p->ch <= 'Z'))) {
|
||||||
|
string_push(&value, p->ch);
|
||||||
|
lstep(p);
|
||||||
|
}
|
||||||
|
if (strcmp(value.data, "null")) {
|
||||||
|
p->curr_tok = TOK_NULL;
|
||||||
|
} else if (strcmp(value.data, "false")) {
|
||||||
|
p->curr_tok = TOK_FALSE;
|
||||||
|
} else if (strcmp(value.data, "true")) {
|
||||||
|
p->curr_tok = TOK_TRUE;
|
||||||
|
} else {
|
||||||
|
fprintf(
|
||||||
|
stderr, "error: json: illegal keyword \"%s\"\n", value.data);
|
||||||
|
p->curr_tok = TOK_ERROR;
|
||||||
|
}
|
||||||
|
string_destroy(&value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (p->ch == '"') {
|
if (p->ch == '"') {
|
||||||
lstep(p);
|
lstep(p);
|
||||||
|
|
||||||
@ -364,10 +420,9 @@ static inline void lex(JsonParser* p)
|
|||||||
}
|
}
|
||||||
lstep(p);
|
lstep(p);
|
||||||
|
|
||||||
// should be interned
|
|
||||||
char* copy = string_copy(&value);
|
char* copy = string_copy(&value);
|
||||||
|
|
||||||
string_destroy(&value);
|
string_destroy(&value);
|
||||||
|
|
||||||
p->curr_tok = '"';
|
p->curr_tok = '"';
|
||||||
p->curr_val = copy;
|
p->curr_val = copy;
|
||||||
return;
|
return;
|
||||||
|
@ -38,11 +38,16 @@ int main(void)
|
|||||||
http_server_set_user_ctx(server, &cx);
|
http_server_set_user_ctx(server, &cx);
|
||||||
|
|
||||||
http_server_get(server, "/api/products/all", route_get_products_all);
|
http_server_get(server, "/api/products/all", route_get_products_all);
|
||||||
|
http_server_post(
|
||||||
|
server, "/api/products/create", route_post_products_create);
|
||||||
|
http_server_post(
|
||||||
|
server, "/api/products/update", route_post_products_update);
|
||||||
|
|
||||||
http_server_get(
|
http_server_get(
|
||||||
server, "/product_editor/index.html", route_get_product_editor_html);
|
server, "/product_editor/index.html", route_get_product_editor_html);
|
||||||
http_server_get(server,
|
http_server_get(server,
|
||||||
"/product_editor/product_editor.js",
|
"/product_editor/product_editor.js",
|
||||||
route_get_product_editor_html);
|
route_get_product_editor_js);
|
||||||
|
|
||||||
http_server_post(server, "/api/carts/purchase", route_post_carts_purchase);
|
http_server_post(server, "/api/carts/purchase", route_post_carts_purchase);
|
||||||
|
|
||||||
|
@ -104,6 +104,15 @@ void receipts_one_res_destroy(ReceiptsOneRes* m)
|
|||||||
receipts_one_res_product_vec_destroy(&m->products);
|
receipts_one_res_product_vec_destroy(&m->products);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void products_create_req_destroy(ProductsCreateReq* m)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(ProductsCreateReq) == 40, "model has changed");
|
||||||
|
|
||||||
|
free(m->name);
|
||||||
|
free(m->description);
|
||||||
|
free(m->barcode);
|
||||||
|
}
|
||||||
|
|
||||||
char* user_to_json_string(const User* m)
|
char* user_to_json_string(const User* m)
|
||||||
{
|
{
|
||||||
static_assert(sizeof(User) == 40, "model has changed");
|
static_assert(sizeof(User) == 40, "model has changed");
|
||||||
@ -363,6 +372,31 @@ char* receipts_one_res_to_json_string(const ReceiptsOneRes* m)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char* products_create_req_to_json_string(const ProductsCreateReq* m)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(ProductsCreateReq) == 40, "model has changed");
|
||||||
|
|
||||||
|
String string;
|
||||||
|
string_construct(&string);
|
||||||
|
string_pushf(&string,
|
||||||
|
"{"
|
||||||
|
"\"name\":\"%s\","
|
||||||
|
"\"description\":\"%s\","
|
||||||
|
"\"price_dkk_cent\":%ld,"
|
||||||
|
"\"coord_id\":%ld,"
|
||||||
|
"\"barcode\":\"%s\""
|
||||||
|
"}",
|
||||||
|
m->name,
|
||||||
|
m->description,
|
||||||
|
m->price_dkk_cent,
|
||||||
|
m->coord_id,
|
||||||
|
m->barcode);
|
||||||
|
|
||||||
|
char* result = string_copy(&string);
|
||||||
|
string_destroy(&string);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char* key;
|
const char* key;
|
||||||
JsonType type;
|
JsonType type;
|
||||||
@ -456,7 +490,7 @@ int product_from_json(Product* m, const JsonValue* json)
|
|||||||
.description = GET_STR("description"),
|
.description = GET_STR("description"),
|
||||||
.price_dkk_cent = GET_INT("price_dkk_cent"),
|
.price_dkk_cent = GET_INT("price_dkk_cent"),
|
||||||
.coord_id = GET_INT("coord_id"),
|
.coord_id = GET_INT("coord_id"),
|
||||||
.barcode = GET_STR("y"),
|
.barcode = GET_STR("barcode"),
|
||||||
};
|
};
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -583,6 +617,29 @@ int receipts_one_res_from_json(ReceiptsOneRes* m, const JsonValue* json)
|
|||||||
PANIC("not implemented");
|
PANIC("not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int products_create_req_from_json(ProductsCreateReq* m, const JsonValue* json)
|
||||||
|
{
|
||||||
|
static_assert(sizeof(ProductsCreateReq) == 40, "model has changed");
|
||||||
|
|
||||||
|
ObjField fields[] = {
|
||||||
|
{ "name", JsonType_String },
|
||||||
|
{ "description", JsonType_String },
|
||||||
|
{ "price_dkk_cent", JsonType_Number },
|
||||||
|
{ "coord_id", JsonType_Number },
|
||||||
|
{ "barcode", JsonType_String },
|
||||||
|
};
|
||||||
|
if (!OBJ_CONFORMS(json, fields))
|
||||||
|
return -1;
|
||||||
|
*m = (ProductsCreateReq) {
|
||||||
|
.name = GET_STR("name"),
|
||||||
|
.description = GET_STR("description"),
|
||||||
|
.price_dkk_cent = GET_INT("price_dkk_cent"),
|
||||||
|
.coord_id = GET_INT("coord_id"),
|
||||||
|
.barcode = GET_STR("barcode"),
|
||||||
|
};
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_VEC_IMPL(ProductPrice, ProductPriceVec, product_price_vec, )
|
DEFINE_VEC_IMPL(ProductPrice, ProductPriceVec, product_price_vec, )
|
||||||
DEFINE_VEC_IMPL(ReceiptProduct, ReceiptProductVec, receipt_product_vec, )
|
DEFINE_VEC_IMPL(ReceiptProduct, ReceiptProductVec, receipt_product_vec, )
|
||||||
DEFINE_VEC_IMPL(Receipt, ReceiptVec, receipt_vec, )
|
DEFINE_VEC_IMPL(Receipt, ReceiptVec, receipt_vec, )
|
||||||
|
@ -121,3 +121,13 @@ typedef struct {
|
|||||||
} ReceiptsOneRes;
|
} ReceiptsOneRes;
|
||||||
|
|
||||||
void receipts_one_res_destroy(ReceiptsOneRes* model);
|
void receipts_one_res_destroy(ReceiptsOneRes* model);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* name;
|
||||||
|
char* description;
|
||||||
|
int64_t price_dkk_cent;
|
||||||
|
int64_t coord_id;
|
||||||
|
char* barcode;
|
||||||
|
} ProductsCreateReq;
|
||||||
|
|
||||||
|
void products_create_req_destroy(ProductsCreateReq* model);
|
||||||
|
@ -17,3 +17,4 @@ DEFINE_MODEL_JSON(SessionsLoginReq, sessions_login_req)
|
|||||||
DEFINE_MODEL_JSON(CartsPurchaseReq, carts_purchase_req)
|
DEFINE_MODEL_JSON(CartsPurchaseReq, carts_purchase_req)
|
||||||
DEFINE_MODEL_JSON(ReceiptsOneResProduct, receipts_one_res_product)
|
DEFINE_MODEL_JSON(ReceiptsOneResProduct, receipts_one_res_product)
|
||||||
DEFINE_MODEL_JSON(ReceiptsOneRes, receipts_one_res)
|
DEFINE_MODEL_JSON(ReceiptsOneRes, receipts_one_res)
|
||||||
|
DEFINE_MODEL_JSON(ProductsCreateReq, products_create_req)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user