diff --git a/src/main/include/models.h b/src/main/include/models.h index 24406e0..6072be3 100644 --- a/src/main/include/models.h +++ b/src/main/include/models.h @@ -8,8 +8,6 @@ struct klist_user { ssize_t id; unsigned char *name; ssize_t local_id; - ssize_t discord_id; - ssize_t google_id; }; typedef struct klist_user klist_user; @@ -51,6 +49,7 @@ klist_list *klist_list_init(); klist_list *klist_list_init_from_sql(sqlite3_stmt *); klist_list *klist_list_get_by_id(klist *, u_int); klist_list **klist_list_get_all_by_user(klist *, u_int, size_t *); +klist_list *klist_list_get_by_user_and_name(klist *, u_int, const char *); void klist_list_save(klist *, klist_list *); void klist_list_delete(klist *, klist_list *); void klist_list_deinit(klist_list *); @@ -60,12 +59,17 @@ struct klist_task { ssize_t id; unsigned char *name; unsigned char *desc; + int stage_id; + time_t due; + int target_stage; + }; typedef struct klist_task klist_task; klist_task *klist_task_init(); klist_task *klist_task_init_from_sql(sqlite3_stmt *); klist_task *klist_task_get_by_id(klist *, u_int); +klist_task **klist_task_get_for_list(klist *, u_int, u_int *); void klist_task_save(klist *, klist_task *); void klist_task_delete(klist *, klist_task *); -void klist_task_deinit(klist_task *); \ No newline at end of file +void klist_task_deinit(klist_task *); diff --git a/src/main/include/sql.h b/src/main/include/sql.h index c6a0fdd..d9b678b 100644 --- a/src/main/include/sql.h +++ b/src/main/include/sql.h @@ -3,21 +3,18 @@ enum KLIST_SQL { INIT, + ADD_LOGIN, ADD_USER, GET_USER, GET_USER_BY_LOCAL, - GET_USER_BY_DISCORD, - GET_USER_BY_GOOGLE, GET_USERS, MOD_USER_NAME, - MOD_USER_LOCAL, - MOD_USER_DISCORD, - MOD_USER_GOOGLE, DEL_USER, ADD_LIST, GET_LIST, GET_LISTS, GET_LISTS_BY_USER, + GET_LISTS_BY_USER_NAME, MOD_LIST_NAME, DEL_LIST, ADD_STAGE, @@ -27,10 +24,11 @@ enum KLIST_SQL { ADD_TASK, GET_TASK, GET_TASKS, + GET_TASKS_FOR_LIST, MOD_TASK_NAME, MOD_TASK_DESCRIPTION, DEL_TASK, _KLIST_SQL_COUNT }; -const char *klist_sql_get(const enum KLIST_SQL); \ No newline at end of file +const char *klist_sql_get(const enum KLIST_SQL); diff --git a/src/main/main.c b/src/main/main.c index 94033e2..4d5d737 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -20,6 +20,7 @@ int main(int argc, char **argv) { if (!ctx->error) { setup(ctx, argc, argv); klist_user *user = klist_user_get_by_local(ctx, getuid()); + int i = 0; switch (ctx->cmd) { case USER: switch (((klist_user_context *)ctx->cmd_ctx)->cmd) { @@ -27,8 +28,7 @@ int main(int argc, char **argv) { if (user) { fprintf( stderr, - "User: %p\nID: %lu\nDiscord: %lu\nGoogle: %lu\n,", - user->name, user->id, user->discord_id, user->google_id + "User: %p\nID: %lu\n", user->name, user->id ); size_t lists_len, i = 0; klist_list **lists = klist_list_get_all_by_user(ctx, user->id, &lists_len); @@ -53,15 +53,16 @@ int main(int argc, char **argv) { } case LIST: klist_list_context *list_ctx = ctx->cmd_ctx; + klist_list *list = NULL; + if (!list_ctx->name) { fprintf(stderr, "Missing name.\n"); break; } switch (list_ctx->cmd) { case LIST_ADD: - if (!list_ctx->name) { fprintf(stderr, "Missing name.\n"); break; } if (!list_ctx->stages_len) { fprintf(stderr, "Missing stages.\n"); break; } - klist_list *list = klist_list_init(); + list = klist_list_init(); list->name = (unsigned char*)list_ctx->name; list->desc = (unsigned char*)list_ctx->desc; klist_list_save(ctx, list); - int i = 0; + i = 0; for (; i < list_ctx->stages_len; i++) { klist_stage *stage = klist_stage_init(); stage->name = (unsigned char*)list_ctx->stages[i]; @@ -69,13 +70,38 @@ int main(int argc, char **argv) { klist_stage_save(ctx, stage); klist_stage_deinit(stage); } - klist_list_deinit(list); break; case LIST_EDIT: - case LIST_GET: - case LIST_DELETE: fprintf(stderr, "Not implemented\n"); + break; + case LIST_GET: + klist_assure_user(ctx, getuid(), getlogin()); + user = klist_user_get_by_local(ctx, getuid()); + list = klist_list_get_by_user_and_name(ctx, user->id, list_ctx->name); + if (list) { + u_int tasks_len = 0; + klist_task **tasks = klist_task_get_for_list(ctx, list->id, &tasks_len); + fprintf(stderr, "Name: %s\nDescription: %s\nTasks: %d\n", list->name, list->desc, tasks_len); + int i = 0; + for(; i < tasks_len; i++) klist_task_deinit(tasks[i]); + free(tasks); + } else + fprintf(stderr, "List '%s' not found.\n", list_ctx->name); + break; + case LIST_DELETE: + list = klist_list_get_by_user_and_name(ctx, user->id, list_ctx->name); + if (list) { + u_int tasks_len = 0; + klist_task **tasks = klist_task_get_for_list(ctx, list->id, &tasks_len); + int i = 0; + for (; i < tasks_len; i++) { + klist_task_delete(ctx, tasks[i]); + klist_task_deinit(tasks[i]); + } + free(tasks); + } } + if (!list) klist_list_deinit(list); case TASK: switch (((klist_task_context *)ctx->cmd_ctx)->cmd) { case TASK_ADD: @@ -195,4 +221,4 @@ void setup(klist *ctx, int argc, char **argv) { fprintf(stderr, "How did we land here?! Pls report argv[1] = %s\n", argv[1]); print_help(argv); } -} \ No newline at end of file +} diff --git a/src/main/models.c b/src/main/models.c index 3c6f34f..9f987db 100644 --- a/src/main/models.c +++ b/src/main/models.c @@ -11,8 +11,6 @@ klist_user *klist_user_init() { user->id = -1; user->name = NULL; user->local_id = -1; - user->discord_id = -1; - user->google_id = -1; return user; } @@ -24,9 +22,6 @@ klist_user *klist_user_init_from_sql(sqlite3_stmt *stmt) { user->name = malloc(bytes); memcpy(user->name, sqlite3_column_text(stmt, 1), bytes); } - if (sqlite3_column_type(stmt, 2) != SQLITE_NULL) user->local_id = sqlite3_column_int(stmt, 2); - if (sqlite3_column_type(stmt, 3) != SQLITE_NULL) user->discord_id = sqlite3_column_int(stmt, 3); - if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) user->google_id = sqlite3_column_int(stmt, 4); return user; } @@ -34,7 +29,7 @@ klist_user *klist_user_get_by_id(klist *ctx, u_int id) { sqlite3_stmt *stmt = ctx->stmts[GET_USER]; sqlite3_bind_int(stmt, 1, id); if (sqlite3_step(stmt) == SQLITE_ROW) { - sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); return klist_user_init_from_sql(stmt); } return NULL; @@ -44,7 +39,7 @@ klist_user *klist_user_get_by_local(klist *ctx, u_int local_id) { sqlite3_stmt *stmt = ctx->stmts[GET_USER_BY_LOCAL]; sqlite3_bind_int(stmt, 1, local_id); if (sqlite3_step(stmt) == SQLITE_ROW) { - sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); return klist_user_init_from_sql(stmt); } return NULL; @@ -54,20 +49,28 @@ void klist_user_save(klist *ctx, klist_user *user) { sqlite3_stmt *stmt = ctx->stmts[ADD_USER]; int result = 0; sqlite3_bind_text(stmt, 1, (char*)user->name, -1, SQLITE_STATIC); - sqlite3_bind_int(stmt, 2, user->local_id); - sqlite3_bind_int(stmt, 3, user->discord_id > 0 ? user->discord_id : -1); - sqlite3_bind_int(stmt, 4, user->google_id > 0 ? user->google_id : -1); - if ((result = sqlite3_step(stmt)) == SQLITE_ROW) { + if ((result = sqlite3_step(stmt)) == SQLITE_ROW) user->id = sqlite3_column_int(stmt, 1); - sqlite3_reset(stmt); - } else fprintf(stderr, "failed to save user: %s - %d\n", sqlite3_errmsg(ctx->db), result); + else + fprintf(stderr, "failed to save user: %s - %d\n", sqlite3_errmsg(ctx->db), result); + sqlite3_clear_bindings(stmt); + + if (user->local_id != -1) { + sqlite3_stmt *stmt = ctx->stmts[ADD_LOGIN]; + int result = 0; + sqlite3_bind_int(stmt, 1, user->id); + sqlite3_bind_int(stmt, 2, user->local_id); + if ((result = sqlite3_step(stmt)) == SQLITE_ROW) + fprintf(stderr, "failed to save logins for user: %s - %d\n", sqlite3_errmsg(ctx->db), result); + sqlite3_clear_bindings(stmt); + } } void klist_user_delete(klist *ctx, klist_user *user) { sqlite3_stmt *stmt = ctx->stmts[DEL_USER]; sqlite3_bind_int(stmt, 1, (int)user->id); if (sqlite3_step(stmt) == SQLITE_ROW) - sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); } void klist_user_deinit(klist_user *user) { @@ -104,12 +107,23 @@ klist_stage *klist_stage_get_by_id(klist *ctx, u_int id) { sqlite3_stmt *stmt = ctx->stmts[GET_STAGE]; sqlite3_bind_int(stmt, 1, id); if (sqlite3_step(stmt) == SQLITE_ROW) { - sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); return klist_stage_init_from_sql(stmt); } return NULL; } +void klist_stage_save(klist *ctx, klist_stage *stage) { + sqlite3_stmt *stmt = ctx->stmts[ADD_STAGE]; + int result = 0; + sqlite3_bind_text(stmt, 1, (char*)stage->name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, (char*)stage->name, -1, SQLITE_STATIC); + if ((result = sqlite3_step(stmt)) == SQLITE_ROW) { + stage->id = sqlite3_column_int(stmt, 1); + sqlite3_clear_bindings(stmt); + } else fprintf(stderr, "failed to save stage: %s - %d\n", sqlite3_errmsg(ctx->db), result); +} + void klist_stage_deinit(klist_stage *stage) { if (stage->name) free(stage->name); if (stage->desc) free(stage->desc); @@ -144,7 +158,7 @@ klist_list *klist_list_get_by_id(klist *ctx, u_int id) { sqlite3_stmt *stmt = ctx->stmts[GET_LIST]; sqlite3_bind_int(stmt, 1, id); if (sqlite3_step(stmt) == SQLITE_ROW) { - sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); return klist_list_init_from_sql(stmt); } return NULL; @@ -157,13 +171,39 @@ klist_list **klist_list_get_all_by_user(klist *ctx, u_int user_id, size_t *count size_t lists_len = 0; while (sqlite3_step(stmt) == SQLITE_ROW) { lists_len++; - lists = realloc(lists, lists_len * sizeof(klist_list *)); - lists[lists_len - 1] = klist_list_init(stmt); + klist_list **_lists = realloc(lists, lists_len * sizeof(klist_list *)); + if (_lists) { + lists = _lists; + lists[lists_len - 1] = klist_list_init(stmt); + } } if (count != NULL) *count = lists_len; return lists; } +klist_list *klist_list_get_by_user_and_name(klist *ctx, u_int user_id, const char *name) { + sqlite3_stmt *stmt = ctx->stmts[GET_LISTS_BY_USER]; + sqlite3_bind_int(stmt, 1, user_id); + sqlite3_bind_text(stmt, 2, name, -1, SQLITE_STATIC); + + while (sqlite3_step(stmt) == SQLITE_ROW) { + sqlite3_clear_bindings(stmt); + return klist_list_init(stmt); + } + return NULL; +} + +void klist_list_save(klist *ctx, klist_list *list) { + sqlite3_stmt *stmt = ctx->stmts[ADD_LIST]; + int result = 0; + sqlite3_bind_text(stmt, 1, (char*)list->name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, (char*)list->desc, -1, SQLITE_STATIC); + if ((result = sqlite3_step(stmt)) == SQLITE_ROW) { + list->id = sqlite3_column_int(stmt, 1); + sqlite3_clear_bindings(stmt); + } else fprintf(stderr, "failed to save list: %s - %d\n", sqlite3_errmsg(ctx->db), result); +} + void klist_list_deinit(klist_list *list) { if (list->name) free(list->name); if (list->desc) free(list->desc); @@ -198,12 +238,48 @@ klist_task *klist_task_get_by_id(klist *ctx, u_int id) { sqlite3_stmt *stmt = ctx->stmts[GET_TASK]; sqlite3_bind_int(stmt, 1, id); if (sqlite3_step(stmt) == SQLITE_ROW) { - sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); return klist_task_init_from_sql(stmt); } return NULL; } +klist_task **klist_task_get_for_list(klist *ctx, u_int list_id, u_int *count) { + sqlite3_stmt *stmt = ctx->stmts[GET_TASKS_FOR_LIST]; + sqlite3_bind_int(stmt, 1, list_id); + klist_task **tasks = NULL; + size_t tasks_len = 0; + while (sqlite3_step(stmt) == SQLITE_ROW) { + tasks_len++; + tasks = realloc(tasks, tasks_len * sizeof(klist_task *)); + tasks[tasks_len - 1] = klist_task_init_from_sql(stmt); + } + sqlite3_clear_bindings(stmt); + *count = tasks_len; + return tasks; +} + +void klist_task_save(klist *ctx, klist_task *task) { + sqlite3_stmt *stmt = ctx->stmts[ADD_TASK]; + int result = 0; + sqlite3_bind_text(stmt, 1, (char*)task->name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, (char*)task->desc, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 3, task->stage_id); + if (task->due) sqlite3_bind_int(stmt, 4, task->due); + if (task->target_stage) sqlite3_bind_int(stmt, 5, task->target_stage); + if ((result = sqlite3_step(stmt)) == SQLITE_ROW) { + task->id = sqlite3_column_int(stmt, 1); + sqlite3_clear_bindings(stmt); + } else fprintf(stderr, "failed to save list: %s - %d\n", sqlite3_errmsg(ctx->db), result); +} +void klist_task_delete(klist *ctx, klist_task *task) { + sqlite3_stmt *stmt = ctx->stmts[DEL_TASK]; + sqlite3_bind_int(stmt, 1, task->id); + if (sqlite3_step(stmt) == SQLITE_DONE) + sqlite3_clear_bindings(stmt); + else fprintf(stderr, "failed to delete task: %s\n", sqlite3_errmsg(ctx->db)); +} + void klist_task_deinit(klist_task *task) { if (task->name) free(task->name); if (task->desc) free(task->desc); diff --git a/src/main/sql.c b/src/main/sql.c index baa2ec1..db3e111 100644 --- a/src/main/sql.c +++ b/src/main/sql.c @@ -6,15 +6,20 @@ const char *klist_sql_get(const enum KLIST_SQL sql) { case INIT: return "create table if not exists users (\n" " id integer primary key,\n" - " name text not null,\n" - " local_id integer, -- if the thing is running locally\n" - " discord_id integer, -- if logging in via web via discord\n" - " google_id integer -- if logging in via web via google\n" + " name text not null\n" + ");\n" + "create table if not exists user_logins (\n" + " id integer primary key,\n" + " user_id integer not null,\n" + " local_id integer,\n" + " foreign key (user_id) references users(id)" ");\n" "create table if not exists user_lists (\n" " id integer primary key,\n" " user_id integer not null,\n" - " list_id integer not null\n" + " list_id integer not null,\n" + " foreign key (user_id) references users(id),\n" + " foreign key (list_id) references lists(id)\n" ");\n" "create table if not exists lists (\n" " id integer primary key,\n" @@ -35,34 +40,32 @@ const char *klist_sql_get(const enum KLIST_SQL sql) { " desc text,\n" " stage integer not null,\n" " due date,\n" - " due_stage integer, -- more like target stage\n" + " target_stage integer, -- more like target stage\n" " foreign key (stage) references task_stages(id),\n" - " foreign key (due_stage) references task_stages(id)\n" + " foreign key (target_stage) references task_stages(id)\n" ")"; - case ADD_USER: return "insert into users (name, local_id, discord_id, google_id) values (?1, ?2, ?3, ?4) returning id"; - case GET_USER: return "select * from users where id = ?1"; - case GET_USER_BY_LOCAL: return "select * from users where local_id = ?1"; - case GET_USER_BY_DISCORD: return "select * from users where discord_id = ?1"; - case GET_USER_BY_GOOGLE: return "select * from users where google_id = ?1"; + case ADD_LOGIN: return "insert into user_logins (user_id, local_id) values (?1, ?2) on conflict (id) do update set user_id=excluded.user_id, local_id=excluded.local_id returning id"; + case ADD_USER: return "insert into users (name) values (?1) on conflict (id) do update set name=excluded.name returning id"; + case GET_USER: return "select users.*, user_logins.local_id from users inner join user_logins on user_logins.user_id = users.id where users.id = ?1"; + case GET_USER_BY_LOCAL: return "select * from users inner join user_logins on user_logins.user_id = users.id where user_logins.local_id = ?1"; case GET_USERS: return "select * from users"; case MOD_USER_NAME: return "update users set name = ?1 where id = ?2"; - case MOD_USER_LOCAL: return "update users set local_id = ?1 where id = ?2";; - case MOD_USER_DISCORD: return "update users set discord_id = ?1 where id = ?2"; - case MOD_USER_GOOGLE: return "update users set google_id = ?1 where id = ?2"; case DEL_USER: return "delete from users where id = ?1"; case ADD_LIST: return "insert into lists (name, desc) values (?1, ?2)"; case GET_LIST: return "select * from lists where id = ?1"; case GET_LISTS: return "select * from lists"; - case GET_LISTS_BY_USER: return "select * from lists where id = (select list_id from user_lists where user_id = ?1)"; + case GET_LISTS_BY_USER: return "select * from lists inner join user_lists on user_lists.list_id = lists.id where user_lists.user_id = ?1"; + case GET_LISTS_BY_USER_NAME: return "select * from lists inner join user_lists on user_lists.list_id = lists.id where user_lists.user_id = ?1 and lists.name like '?2'"; case MOD_LIST_NAME: return "update lists set name = ?1 where id = ?2"; case DEL_LIST: return "delete from lists where id = ?1"; case ADD_STAGE: return "insert into task_stages (name, desc, list_id) values (?1, ?2, ?3)"; case GET_STAGE: return "select * from task_stages where id = ?1"; case GET_STAGES_FOR_LIST: return "select * from task_stages"; case DEL_STAGE: return "delete from task_stages where id = ?1"; - case ADD_TASK: return "insert into tasks (name, desc, stage, due, due_stage) values (?1, ?2, ?3, ?4, ?5);"; + case ADD_TASK: return "insert into tasks (name, desc, stage, due, target_stage) values (?1, ?2, ?3, ?4, ?5);"; case GET_TASK: return "select * from tasks where id = ?1"; case GET_TASKS: return "select * from tasks;"; + case GET_TASKS_FOR_LIST: return "select tasks.* from tasks join task_stages on task_stages.id = tasks.stage where task_stages.list_id = ?1"; case MOD_TASK_NAME: return "update tasks set name = ?1 where id = ?2"; case MOD_TASK_DESCRIPTION: return "update tasks set desc = ?1 where id = ?2"; case DEL_TASK: return "delete from tasks where id = ?1"; diff --git a/src/main/util.c b/src/main/util.c index 4d7ee67..b98c453 100644 --- a/src/main/util.c +++ b/src/main/util.c @@ -79,12 +79,16 @@ void klist_sql_prepare(klist *ctx, char *db) { fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(ctx->db)); return; } + char *errmsg = NULL; + sqlite3_exec(ctx->db, klist_sql_get(INIT), NULL, NULL, &errmsg); + if (errmsg) { + fprintf(stderr, "Database initialization failed (%s), expect issues.\n", sqlite3_errmsg(ctx->db)); + sqlite3_free(errmsg); + } int i = 0; for (; i < _KLIST_SQL_COUNT; i++) if (sqlite3_prepare(ctx->db, klist_sql_get(i), -1, &ctx->stmts[i], NULL) != SQLITE_OK) fprintf(stderr, "sqlite3_prepare: %s\n", sqlite3_errmsg(ctx->db)); - if (sqlite3_step(ctx->stmts[INIT]) != SQLITE_DONE) - fprintf(stderr, "Database initialization failed (%s), expect issues.\n", sqlite3_errmsg(ctx->db)); } /*