#ifndef OATPP_AUTHKIT_DB_USER_PERMISSION_DB_HPP #define OATPP_AUTHKIT_DB_USER_PERMISSION_DB_HPP // DbClient + declarative schema for user_property_permissions and // user_group_permissions (authkit#14 PRs 2 & 3). // // Cross-table effective-permission queries that join consumer-side // tables (e.g. fewo's property_set_members) stay in the consumer — only // the standalone DbClient queries that operate on these two tables move // here. #include "oatpp-authkit/dto/UserPermissionDto.hpp" #include "oatpp-authkit/repo/SchemaContract.hpp" #include "oatpp-sqlite/orm.hpp" #include OATPP_CODEGEN_BEGIN(DbClient) namespace oatpp_authkit::db { /** * @brief DbClient for user_property_permissions and user_group_permissions. */ class UserPermissionDb : public oatpp::orm::DbClient { public: UserPermissionDb(const std::shared_ptr& executor) : oatpp::orm::DbClient(executor) {} // ---- user_property_permissions ---- QUERY(getAllPropertyPermissions, "SELECT * FROM user_property_permissions " "WHERE valid_from <= datetime('now') AND valid_until > datetime('now');") QUERY(getAllPropertyPermissionsRaw, "SELECT * FROM user_property_permissions;") QUERY(getPropertyPermissionsForUser, "SELECT * FROM user_property_permissions " "WHERE user_id = :userId " " AND valid_from <= datetime('now') AND valid_until > datetime('now');", PARAM(oatpp::String, userId)) QUERY(getPropertyPermissionByEntityId, "SELECT * FROM user_property_permissions " "WHERE entity_id = :entityId " " AND valid_from <= datetime('now') AND valid_until > datetime('now');", PARAM(oatpp::String, entityId)) QUERY(upsertPropertyPermissionById, "INSERT INTO user_property_permissions " " (id, entity_id, user_id, property_id, permission, valid_from, valid_until) " "VALUES " " (:p.id, :p.entityId, :p.userId, :p.propertyId, :p.permission, " " :p.validFrom, :p.validUntil) " "ON CONFLICT(id) DO UPDATE SET " " entity_id = excluded.entity_id, " " user_id = excluded.user_id, " " property_id = excluded.property_id, " " permission = excluded.permission, " " valid_from = excluded.valid_from, " " valid_until = excluded.valid_until;", PARAM(oatpp::Object, p)) QUERY(softDeletePropertyPermission, "UPDATE user_property_permissions SET valid_until = datetime('now') " "WHERE entity_id = :entityId AND valid_until > datetime('now');", PARAM(oatpp::String, entityId)) // ---- user_group_permissions ---- QUERY(getAllGroupPermissions, "SELECT * FROM user_group_permissions " "WHERE valid_from <= datetime('now') AND valid_until > datetime('now');") QUERY(getAllGroupPermissionsRaw, "SELECT * FROM user_group_permissions;") QUERY(getGroupPermissionsForUser, "SELECT * FROM user_group_permissions " "WHERE user_id = :userId " " AND valid_from <= datetime('now') AND valid_until > datetime('now');", PARAM(oatpp::String, userId)) QUERY(getGroupPermissionByEntityId, "SELECT * FROM user_group_permissions " "WHERE entity_id = :entityId " " AND valid_from <= datetime('now') AND valid_until > datetime('now');", PARAM(oatpp::String, entityId)) QUERY(upsertGroupPermissionById, "INSERT INTO user_group_permissions " " (id, entity_id, user_id, set_id, permission, valid_from, valid_until) " "VALUES " " (:p.id, :p.entityId, :p.userId, :p.setId, :p.permission, " " :p.validFrom, :p.validUntil) " "ON CONFLICT(id) DO UPDATE SET " " entity_id = excluded.entity_id, " " user_id = excluded.user_id, " " set_id = excluded.set_id, " " permission = excluded.permission, " " valid_from = excluded.valid_from, " " valid_until = excluded.valid_until;", PARAM(oatpp::Object, p)) QUERY(softDeleteGroupPermission, "UPDATE user_group_permissions SET valid_until = datetime('now') " "WHERE entity_id = :entityId AND valid_until > datetime('now');", PARAM(oatpp::String, entityId)) }; /** * @brief Declarative schema for `user_property_permissions`. * * Composes with `TemporalRepository` to produce * the full table including the temporal `valid_until` + composite UNIQUE * index. The natural-key UNIQUE `(user_id, property_id, valid_until)` is * carried as an explicit index here so duplicate live grants for the * same (user, property) pair are prevented at the DB level. */ struct UserPropertyPermissionSchema { inline static constexpr repo::ColumnSpec kColumns[] = { {"id", "TEXT PRIMARY KEY"}, {"entity_id", "TEXT NOT NULL"}, {"user_id", "TEXT NOT NULL"}, {"property_id", "TEXT NOT NULL"}, {"permission", "TEXT NOT NULL DEFAULT 'readonly'"}, // valid_from / valid_until come from TemporalRepository. }; inline static constexpr repo::IndexSpec kIndexes[] = { {"ix_{table}_entity_id", false, "(entity_id)"}, {"ix_{table}_user_id", false, "(user_id)"}, {"ux_{table}_user_property_until", true, "(user_id, property_id, valid_until)"}, }; inline static constexpr repo::DecoratorSchema kSchema = { "UserPropertyPermissionSchema", kColumns, sizeof(kColumns)/sizeof(kColumns[0]), kIndexes, sizeof(kIndexes)/sizeof(kIndexes[0]), nullptr, 0, }; }; /** * @brief Declarative schema for `user_group_permissions`. * * Mirrors `UserPropertyPermissionSchema` with `set_id` instead of * `property_id`. The natural-key UNIQUE prevents duplicate live grants * for the same (user, set) pair. */ struct UserGroupPermissionSchema { inline static constexpr repo::ColumnSpec kColumns[] = { {"id", "TEXT PRIMARY KEY"}, {"entity_id", "TEXT NOT NULL"}, {"user_id", "TEXT NOT NULL"}, {"set_id", "TEXT NOT NULL"}, {"permission", "TEXT NOT NULL DEFAULT 'readonly'"}, }; inline static constexpr repo::IndexSpec kIndexes[] = { {"ix_{table}_entity_id", false, "(entity_id)"}, {"ix_{table}_user_id", false, "(user_id)"}, {"ux_{table}_user_set_until", true, "(user_id, set_id, valid_until)"}, }; inline static constexpr repo::DecoratorSchema kSchema = { "UserGroupPermissionSchema", kColumns, sizeof(kColumns)/sizeof(kColumns[0]), kIndexes, sizeof(kIndexes)/sizeof(kIndexes[0]), nullptr, 0, }; }; } // namespace oatpp_authkit::db #include OATPP_CODEGEN_END(DbClient) #endif