Skip to content

Instantly share code, notes, and snippets.

@tashima42
Last active April 7, 2026 01:46
Show Gist options
  • Select an option

  • Save tashima42/6c27098ae93268b902af9ea91a1331a9 to your computer and use it in GitHub Desktop.

Select an option

Save tashima42/6c27098ae93268b902af9ea91a1331a9 to your computer and use it in GitHub Desktop.
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2026 Pedro H. M. Tashima
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <jansson.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/_pthread/_pthread_mutex_t.h>
#include "../papago.h"
static papago_t *server = NULL;
typedef struct {
char name[64];
char category[64];
} deal_t;
#define MAX_RECORDS 100
typedef struct {
deal_t records[MAX_RECORDS];
size_t count;
pthread_mutex_t mu;
} deals_store_t;
void init_store(deals_store_t *store) {
store->count = 0;
memset(store->records, 0, sizeof(store->records));
pthread_mutex_init(&store->mu, NULL);
}
int add_deal(deals_store_t *store, char *name, char *category) {
pthread_mutex_lock(&store->mu);
if (store->count >= MAX_RECORDS) {
pthread_mutex_unlock(&store->mu);
return -1;
}
int idx = store->count;
strncpy(store->records[idx].name, name, sizeof(store->records[idx].name) - 1);
strncpy(store->records[idx].category, category,
sizeof(store->records[idx].category) - 1);
store->count++;
pthread_mutex_unlock(&store->mu);
return 0;
}
/**
* Signal handler for graceful shutdown.
*/
static void signal_handler(int sig) {
PAPAGO_UNUSED(sig);
printf("\nShutting down...\n");
if (server != NULL) {
papago_stop(server);
}
}
// HTTP route handlers
void api_list_deals_handler(papago_request_t *req, papago_response_t *res,
void *user_data) {
PAPAGO_UNUSED(req);
deals_store_t *store = (deals_store_t *)user_data;
pthread_mutex_lock(&store->mu);
if (!store) {
papago_res_status(res, PAPAGO_STATUS_INTERNAL_ERROR);
papago_res_json(res, "{\"error\": \"internal server error\"}");
pthread_mutex_unlock(&store->mu);
return;
}
json_t *json_arr = json_array();
if (!json_arr) {
papago_res_status(res, PAPAGO_STATUS_INTERNAL_ERROR);
papago_res_json(res, "{\"error\": \"internal server error\"}");
pthread_mutex_unlock(&store->mu);
return;
}
for (size_t i = 0; i < store->count; ++i) {
json_t *record_obj = json_pack("{s:s, s:s}",
"name", store->records[i].name,
"category", store->records[i].category);
if (record_obj) {
json_array_append_new(json_arr, record_obj);
}
}
char *json_str = json_dumps(json_arr, JSON_COMPACT);
if (json_str) {
papago_res_status(res, PAPAGO_STATUS_OK);
papago_res_json(res, json_str);
free(json_str);
}
json_decref(json_arr);
pthread_mutex_unlock(&store->mu);
}
void api_add_deal_handler(papago_request_t *req, papago_response_t *res,
void *user_data) {
PAPAGO_UNUSED(req);
deals_store_t *store = (deals_store_t *)user_data;
const char *body = papago_req_body(req);
if (body == NULL) {
papago_res_status(res, PAPAGO_STATUS_BAD_REQUEST);
papago_res_json(res, "{\"error\":\"Missing request body\"}");
return;
}
json_error_t error;
json_t *root = json_loads(body, 0, &error);
if (!root) {
fprintf(stderr, "JSON Parse Error on line %d: %s\n", error.line, error.text);
papago_res_status(res, PAPAGO_STATUS_BAD_REQUEST);
papago_res_json(res, "{\"error\":\"Invalid JSON format\"}");
return;
}
// int *id = NULL;
char *name = NULL;
char *category = NULL;
if (json_unpack(root, "{s:s, s:s}", "name", &name, "category", &category) != 0) {
fprintf(stderr, "JSON Parse Error on line %d: %s\n", error.line, error.text);
papago_res_status(res, PAPAGO_STATUS_BAD_REQUEST);
papago_res_json(res, "{\"error\":\"missing or invalid 'id', 'name' or 'category' strings\"}");
json_decref(root);
return;
}
if (add_deal(store, name, category) != 0) {
papago_res_status(res, PAPAGO_STATUS_INTERNAL_ERROR);
papago_res_json(res, "{\"error\":\"failed to save deal\"}");
json_decref(root);
return;
}
papago_res_status(res, PAPAGO_STATUS_OK);
papago_res_json(res, "{\"message\":\"Item successfully parsed!\"}");
json_decref(root);
}
int main(void) {
// setup signal handling
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// create server
server = papago_new();
if (server == NULL) {
fprintf(stderr, "failed to create server\n");
return 1;
}
papago_config_t config = papago_default_config();
config.port = 8282;
config.enable_logging = true;
papago_configure(server, &config);
deals_store_t *store = malloc(sizeof(deals_store_t) * MAX_RECORDS);
init_store(store);
// register HTTP routes
papago_get(server, "/api/deals", api_list_deals_handler, store);
papago_post(server, "/api/deal", api_add_deal_handler, store);
printf("Server starting on:\n");
printf(" HTTP: http://%s:%d\n", config.host, config.port);
printf("curl http://%s:%d/api/deals\n", config.host, config.port);
// start server (blocking)
if (papago_start(server) != 0) {
fprintf(stderr, "%s\n", papago_error(server));
papago_destroy(server);
return 1;
}
// cleanup
papago_destroy(server);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment