#include "commands.h" #include "cmd_util.h" #include #include #include #include "audit.h" #include "config.h" #include "db.h" #include "seed.h" #include "util.h" /* ------------------------------------------------------------------ */ /* orgs */ /* ------------------------------------------------------------------ */ static yyjson_mut_val *org_json(struct req *r, sqlite3_stmt *st) { return db_row_json(r->rdoc, st, "id:i,name:s,org_nr:s,vat_nr:s,address:s," "postal_code:s,city:s,country:s,email:s,phone:s," "fiscal_year_start_month:i,moms_period:s,framework:s," "created_at:s,description:s,shares:i"); } #define ORG_COLUMNS \ "id,name,org_nr,vat_nr,address,postal_code,city,country,email," \ "phone,fiscal_year_start_month,moms_period,framework,created_at," \ "description,shares" static yyjson_mut_val *h_org_create(struct req *r) { if (!g_cfg.allow_org_create && !r->is_admin) return fail(r, "FORBIDDEN", "org creation is disabled"); const char *name = arg_str(r->args, "name"); if (!name || !*name) return fail(r, "INVALID_ARGS", "name is required"); const char *org_nr = arg_str(r->args, "org_nr"); int64_t fy_month = 1; arg_int(r->args, "fiscal_year_start_month", &fy_month); if (fy_month < 1 || fy_month > 12) return fail(r, "INVALID_ARGS", "fiscal_year_start_month must be 1-12"); const char *moms = arg_str(r->args, "moms_period"); if (!moms) moms = "month"; if (strcmp(moms, "month") != 0 && strcmp(moms, "quarter") != 0 && strcmp(moms, "year") != 0) return fail(r, "INVALID_ARGS", "moms_period must be month, quarter or year"); const char *framework = arg_str(r->args, "framework"); if (!framework) framework = "K2"; if (strcmp(framework, "K2") != 0 && strcmp(framework, "K3") != 0) return fail(r, "INVALID_ARGS", "framework must be K2 or K3"); char ts[32]; util_iso8601(util_now(), ts, sizeof ts); if (db_exec(r->db, "BEGIN IMMEDIATE", NULL) != 0) return fail(r, "DB_BUSY", "could not start transaction"); sqlite3_stmt *st = NULL; int rc = sqlite3_prepare_v2( r->db, "INSERT INTO orgs(name,org_nr,fiscal_year_start_month,moms_period," "framework,created_at,created_by) VALUES(?1,?2,?3,?4,?5,?6,?7)", -1, &st, NULL); if (rc != SQLITE_OK) { db_exec(r->db, "ROLLBACK", NULL); return db_sqlite_error(r); } sqlite3_bind_text(st, 1, name, -1, SQLITE_TRANSIENT); bind_text_or_null(st, 2, org_nr); sqlite3_bind_int64(st, 3, fy_month); sqlite3_bind_text(st, 4, moms, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 5, framework, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 6, ts, -1, SQLITE_TRANSIENT); sqlite3_bind_int64(st, 7, r->sess->user_id); rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) { db_exec(r->db, "ROLLBACK", NULL); return db_sqlite_error(r); } int64_t org_id = db_last_id(r->db); rc = sqlite3_prepare_v2( r->db, "INSERT INTO memberships(org_id,user_id,role,created_at)" " VALUES(?1,?2,'owner',?3)", -1, &st, NULL); if (rc != SQLITE_OK) { db_exec(r->db, "ROLLBACK", NULL); return db_sqlite_error(r); } sqlite3_bind_int64(st, 1, org_id); sqlite3_bind_int64(st, 2, r->sess->user_id); sqlite3_bind_text(st, 3, ts, -1, SQLITE_TRANSIENT); rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) { db_exec(r->db, "ROLLBACK", NULL); return db_sqlite_error(r); } int64_t fy_id = 0; char *seed_err = NULL; if (seed_org(r->db, org_id, framework, (int)fy_month, &fy_id, &seed_err) != 0) { db_exec(r->db, "ROLLBACK", NULL); yyjson_mut_val *res = fail(r, "INTERNAL", seed_err ? seed_err : "seeding failed"); free(seed_err); return res; } if (db_exec(r->db, "COMMIT", NULL) != 0) return fail(r, "INTERNAL", "commit failed"); char *reqjson = audit_args_json(r->args); audit_append(r->db, org_id, r->sess->user_id, r->sess->token_id, "org.create", reqjson, "OK", NULL); free(reqjson); r->sess->active_org = org_id; st = NULL; if (sqlite3_prepare_v2(r->db, "SELECT " ORG_COLUMNS " FROM orgs WHERE id=?1", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, org_id); yyjson_mut_val *o = NULL; if (sqlite3_step(st) == SQLITE_ROW) o = org_json(r, st); sqlite3_finalize(st); if (!o) return fail(r, "INTERNAL", "created org not found"); yyjson_mut_obj_add_int(r->rdoc, o, "fiscal_year_id", fy_id); return o; } static yyjson_mut_val *h_org_list(struct req *r) { yyjson_mut_val *items = yyjson_mut_arr(r->rdoc); sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( r->db, "SELECT o.id,o.name,o.org_nr,m.role FROM memberships m" " JOIN orgs o ON o.id=m.org_id WHERE m.user_id=?1 ORDER BY o.id", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, r->sess->user_id); while (sqlite3_step(st) == SQLITE_ROW) { yyjson_mut_val *o = yyjson_mut_arr_add_obj(r->rdoc, items); yyjson_mut_obj_add_int(r->rdoc, o, "id", sqlite3_column_int64(st, 0)); yyjson_mut_obj_add_strcpy(r->rdoc, o, "name", sq(sqlite3_column_text(st, 1))); yyjson_mut_obj_add_strcpy(r->rdoc, o, "org_nr", sq(sqlite3_column_text(st, 2))); yyjson_mut_obj_add_strcpy(r->rdoc, o, "role", sq(sqlite3_column_text(st, 3))); } sqlite3_finalize(st); yyjson_mut_val *out = yyjson_mut_obj(r->rdoc); yyjson_mut_obj_add_val(r->rdoc, out, "items", items); return out; } static yyjson_mut_val *h_org_get(struct req *r) { sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2(r->db, "SELECT " ORG_COLUMNS " FROM orgs WHERE id=?1", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, r->org_id); yyjson_mut_val *o = NULL; if (sqlite3_step(st) == SQLITE_ROW) o = org_json(r, st); sqlite3_finalize(st); if (!o) return fail(r, "NOT_FOUND", "org not found"); return o; } static yyjson_mut_val *h_org_update(struct req *r) { const char *name = arg_str(r->args, "name"); const char *org_nr = arg_str(r->args, "org_nr"); const char *vat_nr = arg_str(r->args, "vat_nr"); const char *address = arg_str(r->args, "address"); const char *postal = arg_str(r->args, "postal_code"); const char *city = arg_str(r->args, "city"); const char *country = arg_str(r->args, "country"); const char *email = arg_str(r->args, "email"); const char *phone = arg_str(r->args, "phone"); const char *moms = arg_str(r->args, "moms_period"); const char *framework = arg_str(r->args, "framework"); const char *description = arg_str(r->args, "description"); int64_t fy_month = 0; arg_int(r->args, "fiscal_year_start_month", &fy_month); int64_t shares = -1; arg_int(r->args, "shares", &shares); if (!name && !org_nr && !vat_nr && !address && !postal && !city && !country && !email && !phone && !moms && !framework && fy_month == 0 && !description && shares < 0) return fail(r, "INVALID_ARGS", "nothing to update"); if (name && !*name) return fail(r, "INVALID_ARGS", "name cannot be empty"); if (fy_month && (fy_month < 1 || fy_month > 12)) return fail(r, "INVALID_ARGS", "fiscal_year_start_month must be 1-12"); if (shares < -1) return fail(r, "INVALID_ARGS", "shares must be >= 0"); if (moms && strcmp(moms, "month") != 0 && strcmp(moms, "quarter") != 0 && strcmp(moms, "year") != 0) return fail(r, "INVALID_ARGS", "moms_period must be month, quarter or year"); if (framework && strcmp(framework, "K2") != 0 && strcmp(framework, "K3") != 0) return fail(r, "INVALID_ARGS", "framework must be K2 or K3"); static const char *const keys[10] = { "name", "org_nr", "vat_nr", "address", "postal_code", "city", "country", "email", "phone", "description" }; const char *texts[10] = { name, org_nr, vat_nr, address, postal, city, country, email, phone, description }; if (r->dry_run) { yyjson_mut_val *o = yyjson_mut_obj(r->rdoc); for (int i = 0; i < 10; i++) if (texts[i]) yyjson_mut_obj_add_strcpy(r->rdoc, o, keys[i], texts[i]); if (fy_month) yyjson_mut_obj_add_int(r->rdoc, o, "fiscal_year_start_month", fy_month); if (moms) yyjson_mut_obj_add_strcpy(r->rdoc, o, "moms_period", moms); if (framework) yyjson_mut_obj_add_strcpy(r->rdoc, o, "framework", framework); if (shares >= 0) yyjson_mut_obj_add_int(r->rdoc, o, "shares", shares); yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true); return o; } sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( r->db, "UPDATE orgs SET" " name=COALESCE(?2,name), org_nr=COALESCE(?3,org_nr)," " vat_nr=COALESCE(?4,vat_nr), address=COALESCE(?5,address)," " postal_code=COALESCE(?6,postal_code), city=COALESCE(?7,city)," " country=COALESCE(?8,country), email=COALESCE(?9,email)," " phone=COALESCE(?10,phone), description=COALESCE(?11," " description)," " fiscal_year_start_month=CASE WHEN ?12=0" " THEN fiscal_year_start_month ELSE ?12 END," " moms_period=COALESCE(?13,moms_period)," " framework=COALESCE(?14,framework)," " shares=CASE WHEN ?15<0 THEN shares ELSE ?15 END" " WHERE id=?1", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, r->org_id); for (int i = 0; i < 10; i++) { bind_text_or_null(st, i + 2, texts[i]); } sqlite3_bind_int64(st, 12, fy_month); bind_text_or_null(st, 13, moms); bind_text_or_null(st, 14, framework); sqlite3_bind_int64(st, 15, shares); int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) return db_sqlite_error(r); char *reqjson = audit_args_json(r->args); audit_append(r->db, r->org_id, r->sess->user_id, r->sess->token_id, "org.update", reqjson, "OK", NULL); free(reqjson); st = NULL; if (sqlite3_prepare_v2(r->db, "SELECT " ORG_COLUMNS " FROM orgs WHERE id=?1", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, r->org_id); yyjson_mut_val *o = NULL; if (sqlite3_step(st) == SQLITE_ROW) o = org_json(r, st); sqlite3_finalize(st); if (!o) return fail(r, "NOT_FOUND", "org not found"); return o; } static yyjson_mut_val *h_org_member_list(struct req *r) { return req_list(r, "SELECT u.id,u.username,u.display_name,m.role FROM memberships m" " JOIN users u ON u.id=m.user_id WHERE m.org_id=?1 ORDER BY u.id", "user_id:i,username:s,display_name:s,role:s", "i", r->org_id); } static int64_t username_to_id(sqlite3 *db, const char *username) { sqlite3_stmt *st = NULL; int64_t id = -1; if (sqlite3_prepare_v2(db, "SELECT id FROM users WHERE username=?1", -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_text(st, 1, username, -1, SQLITE_TRANSIENT); if (sqlite3_step(st) == SQLITE_ROW) id = sqlite3_column_int64(st, 0); sqlite3_finalize(st); return id; } static int64_t owner_count(sqlite3 *db, int64_t org_id) { sqlite3_stmt *st = NULL; int64_t n = -1; if (sqlite3_prepare_v2( db, "SELECT count(*) FROM memberships WHERE org_id=?1 AND role='owner'", -1, &st, NULL) != SQLITE_OK) return -1; sqlite3_bind_int64(st, 1, org_id); if (sqlite3_step(st) == SQLITE_ROW) n = sqlite3_column_int64(st, 0); sqlite3_finalize(st); return n; } static int valid_role(const char *role) { return role && (strcmp(role, "owner") == 0 || strcmp(role, "bookkeeper") == 0 || strcmp(role, "viewer") == 0); } static yyjson_mut_val *h_org_member_add(struct req *r) { const char *username = arg_str(r->args, "username"); const char *role = arg_str(r->args, "role"); if (!username || !valid_role(role)) return fail(r, "INVALID_ARGS", "username and a valid role are required"); int64_t uid = username_to_id(r->db, username); if (uid < 0) return fail(r, "NOT_FOUND", "user not found"); char ts[32]; util_iso8601(util_now(), ts, sizeof ts); sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( r->db, "INSERT INTO memberships(org_id,user_id,role,created_at)" " VALUES(?1,?2,?3,?4)", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, r->org_id); sqlite3_bind_int64(st, 2, uid); sqlite3_bind_text(st, 3, role, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 4, ts, -1, SQLITE_TRANSIENT); int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) return fail(r, "CONFLICT", "user is already a member"); char *reqjson = audit_args_json(r->args); audit_append(r->db, r->org_id, r->sess->user_id, 0, "member.add", reqjson, "OK", NULL); free(reqjson); return yyjson_mut_obj(r->rdoc); } static yyjson_mut_val *h_org_member_set_role(struct req *r) { const char *username = arg_str(r->args, "username"); const char *role = arg_str(r->args, "role"); if (!username || !valid_role(role)) return fail(r, "INVALID_ARGS", "username and a valid role are required"); int64_t uid = username_to_id(r->db, username); if (uid < 0) return fail(r, "NOT_FOUND", "user not found"); char *current = db_membership_role(r->db, r->org_id, uid); if (!current) return fail(r, "NOT_FOUND", "user is not a member"); if (strcmp(current, "owner") == 0 && strcmp(role, "owner") != 0 && owner_count(r->db, r->org_id) <= 1) { free(current); return fail(r, "CONFLICT", "cannot demote the last owner"); } free(current); sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2( r->db, "UPDATE memberships SET role=?1 WHERE org_id=?2 AND user_id=?3", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_text(st, 1, role, -1, SQLITE_TRANSIENT); sqlite3_bind_int64(st, 2, r->org_id); sqlite3_bind_int64(st, 3, uid); int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) return db_sqlite_error(r); char *reqjson = audit_args_json(r->args); audit_append(r->db, r->org_id, r->sess->user_id, 0, "member.set_role", reqjson, "OK", NULL); free(reqjson); return yyjson_mut_obj(r->rdoc); } static yyjson_mut_val *h_org_member_remove(struct req *r) { const char *username = arg_str(r->args, "username"); if (!username) return fail(r, "INVALID_ARGS", "username is required"); int64_t uid = username_to_id(r->db, username); if (uid < 0) return fail(r, "NOT_FOUND", "user not found"); char *current = db_membership_role(r->db, r->org_id, uid); if (!current) return fail(r, "NOT_FOUND", "user is not a member"); if (strcmp(current, "owner") == 0 && owner_count(r->db, r->org_id) <= 1) { free(current); return fail(r, "CONFLICT", "cannot remove the last owner"); } free(current); if (req_exec(r, "DELETE FROM memberships WHERE org_id=?1 AND user_id=?2", "ii", r->org_id, uid) != 0) return NULL; char *reqjson = audit_args_json(r->args); audit_append(r->db, r->org_id, r->sess->user_id, 0, "member.remove", reqjson, "OK", NULL); free(reqjson); return yyjson_mut_obj(r->rdoc); } /* ------------------------------------------------------------------ */ /* board members (årsredovisning signatures) */ /* ------------------------------------------------------------------ */ #define BOARD_SPEC "id:i,name:s,title:s" static yyjson_mut_val *board_json(struct req *r, sqlite3_stmt *st) { return db_row_json(r->rdoc, st, BOARD_SPEC); } static yyjson_mut_val *h_board_list(struct req *r) { return req_list(r, "SELECT id,name,title FROM board_members" " WHERE org_id=?1 ORDER BY id", BOARD_SPEC, "i", r->org_id); } static yyjson_mut_val *board_get(struct req *r, int64_t id) { sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2(r->db, "SELECT id,name,title FROM board_members" " WHERE org_id=?1 AND id=?2", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, r->org_id); sqlite3_bind_int64(st, 2, id); yyjson_mut_val *o = NULL; if (sqlite3_step(st) == SQLITE_ROW) o = board_json(r, st); sqlite3_finalize(st); if (!o) return fail(r, "NOT_FOUND", "board member not found"); return o; } static yyjson_mut_val *h_board_add(struct req *r) { const char *name = arg_str(r->args, "name"); const char *title = arg_str(r->args, "title"); if (!name || !*name) return fail(r, "INVALID_ARGS", "name is required"); if (!title || !*title) title = "Styrelseledamot"; if (r->dry_run) { yyjson_mut_val *o = yyjson_mut_obj(r->rdoc); yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true); yyjson_mut_obj_add_strcpy(r->rdoc, o, "name", name); yyjson_mut_obj_add_strcpy(r->rdoc, o, "title", title); return o; } char ts[32]; util_iso8601(util_now(), ts, sizeof ts); sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2(r->db, "INSERT INTO board_members(org_id,name,title," "created_at) VALUES(?1,?2,?3,?4)", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, r->org_id); sqlite3_bind_text(st, 2, name, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 3, title, -1, SQLITE_TRANSIENT); sqlite3_bind_text(st, 4, ts, -1, SQLITE_TRANSIENT); int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) return db_sqlite_error(r); int64_t id = db_last_id(r->db); char *reqjson = audit_args_json(r->args); audit_append(r->db, r->org_id, r->sess->user_id, r->sess->token_id, "board.add", reqjson, "OK", NULL); free(reqjson); return board_get(r, id); } static yyjson_mut_val *h_board_update(struct req *r) { int64_t id = 0; if (!arg_int(r->args, "id", &id) || id <= 0) return fail(r, "INVALID_ARGS", "id is required"); const char *name = arg_str(r->args, "name"); const char *title = arg_str(r->args, "title"); if (!name && !title) return fail(r, "INVALID_ARGS", "nothing to update"); if (name && !*name) return fail(r, "INVALID_ARGS", "name cannot be empty"); if (title && !*title) return fail(r, "INVALID_ARGS", "title cannot be empty"); if (r->dry_run) { yyjson_mut_val *o = yyjson_mut_obj(r->rdoc); yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true); yyjson_mut_obj_add_int(r->rdoc, o, "id", id); if (name) yyjson_mut_obj_add_strcpy(r->rdoc, o, "name", name); if (title) yyjson_mut_obj_add_strcpy(r->rdoc, o, "title", title); return o; } sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2(r->db, "UPDATE board_members SET name=COALESCE(?3,name)," " title=COALESCE(?4,title)" " WHERE org_id=?1 AND id=?2", -1, &st, NULL) != SQLITE_OK) return db_error(r); sqlite3_bind_int64(st, 1, r->org_id); sqlite3_bind_int64(st, 2, id); bind_text_or_null(st, 3, name); bind_text_or_null(st, 4, title); int rc = sqlite3_step(st); sqlite3_finalize(st); if (rc != SQLITE_DONE) return db_sqlite_error(r); if (sqlite3_changes(r->db) == 0) return fail(r, "NOT_FOUND", "board member not found"); char *reqjson = audit_args_json(r->args); audit_append(r->db, r->org_id, r->sess->user_id, r->sess->token_id, "board.update", reqjson, "OK", NULL); free(reqjson); return board_get(r, id); } static yyjson_mut_val *h_board_remove(struct req *r) { int64_t id = 0; if (!arg_int(r->args, "id", &id) || id <= 0) return fail(r, "INVALID_ARGS", "id is required"); if (r->dry_run) { yyjson_mut_val *o = yyjson_mut_obj(r->rdoc); yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true); yyjson_mut_obj_add_int(r->rdoc, o, "id", id); return o; } if (req_exec(r, "DELETE FROM board_members WHERE org_id=?1 AND id=?2", "ii", r->org_id, id) != 0) return NULL; if (sqlite3_changes(r->db) == 0) return fail(r, "NOT_FOUND", "board member not found"); char *reqjson = audit_args_json(r->args); audit_append(r->db, r->org_id, r->sess->user_id, r->sess->token_id, "board.remove", reqjson, "OK", NULL); free(reqjson); return yyjson_mut_obj(r->rdoc); } static const struct cmd_arg args_org_create[] = { { "name", ARG_STR, 1, NULL, NULL, "Org name" }, { "org_nr", ARG_STR, 0, NULL, NULL, "Swedish org number" }, { "fiscal_year_start_month", ARG_INT, 0, "1", NULL, "Fiscal year start month 1-12" }, { "moms_period", ARG_ENUM, 0, "month", "month,quarter,year", "VAT reporting period" }, { "framework", ARG_ENUM, 0, "K2", "K2,K3", "Accounting framework" }, }; static const struct cmd_arg args_org_update[] = { { "name", ARG_STR, 0, NULL, NULL, "Org name" }, { "org_nr", ARG_STR, 0, NULL, NULL, "Swedish org number" }, { "vat_nr", ARG_STR, 0, NULL, NULL, "VAT number" }, { "address", ARG_STR, 0, NULL, NULL, "Street address" }, { "postal_code", ARG_STR, 0, NULL, NULL, "Postal code" }, { "city", ARG_STR, 0, NULL, NULL, "City" }, { "country", ARG_STR, 0, NULL, NULL, "Country" }, { "email", ARG_STR, 0, NULL, NULL, "E-mail" }, { "phone", ARG_STR, 0, NULL, NULL, "Phone" }, { "moms_period", ARG_ENUM, 0, NULL, "month,quarter,year", "VAT reporting period" }, { "framework", ARG_ENUM, 0, NULL, "K2,K3", "Accounting framework" }, { "description", ARG_STR, 0, NULL, NULL, "Free-text description" }, { "fiscal_year_start_month", ARG_INT, 0, NULL, NULL, "Fiscal year start month 1-12" }, { "shares", ARG_INT, 0, NULL, NULL, "Number of shares" }, }; static const struct cmd_arg args_member_role[] = { { "username", ARG_STR, 1, NULL, NULL, "Username" }, { "role", ARG_ENUM, 1, NULL, "owner,bookkeeper,viewer", "Membership role" }, }; static const struct cmd_arg args_member_username[] = { { "username", ARG_STR, 1, NULL, NULL, "Username" }, }; static const struct cmd_arg args_board_add[] = { { "name", ARG_STR, 1, NULL, NULL, "Board member name" }, { "title", ARG_STR, 0, "Styrelseledamot", NULL, "Role on the board" }, }; static const struct cmd_arg args_board_update[] = { { "id", ARG_INT, 1, NULL, NULL, "Board member id" }, { "name", ARG_STR, 0, NULL, NULL, "Board member name" }, { "title", ARG_STR, 0, NULL, NULL, "Role on the board" }, }; static const struct cmd_arg args_board_remove[] = { { "id", ARG_INT, 1, NULL, NULL, "Board member id" }, }; const struct command g_cmd_org[] = { { "org.create", "Create an org and become its owner", PERM_READ, 0, 1, 1, h_org_create, CMD_ARGS(args_org_create) }, { "org.list", "List orgs for the current user", PERM_READ, 0, 0, 0, h_org_list, NULL, 0 }, { "org.get", "Get org details", PERM_READ, 1, 0, 0, h_org_get, NULL, 0 }, { "org.update", "Update org details (owner)", PERM_OWNER, 1, 1, 1, h_org_update, CMD_ARGS(args_org_update) }, { "org.member_list", "List org members", PERM_READ, 1, 0, 0, h_org_member_list, NULL, 0 }, { "org.member_add", "Add a member to the org", PERM_OWNER, 1, 1, 1, h_org_member_add, CMD_ARGS(args_member_role) }, { "org.member_set_role", "Change a member's role", PERM_OWNER, 1, 1, 1, h_org_member_set_role, CMD_ARGS(args_member_role) }, { "org.member_remove", "Remove a member from the org", PERM_OWNER, 1, 1, 1, h_org_member_remove, CMD_ARGS(args_member_username) }, { "board.list", "List board members", PERM_READ, 1, 0, 0, h_board_list, NULL, 0 }, { "board.add", "Add a board member", PERM_OWNER, 1, 1, 1, h_board_add, CMD_ARGS(args_board_add) }, { "board.update", "Update a board member", PERM_OWNER, 1, 1, 1, h_board_update, CMD_ARGS(args_board_update) }, { "board.remove", "Remove a board member", PERM_OWNER, 1, 1, 1, h_board_remove, CMD_ARGS(args_board_remove) }, }; const struct cmd_table g_cmd_table_org = { g_cmd_org, sizeof g_cmd_org / sizeof g_cmd_org[0] };