Lifts both per-property and per-property-set RBAC tables from fewo-webapp into oatpp-authkit. Combined into one commit because they share a DbClient and the cross-table effective-permission resolver — the resolver itself stays in fewo since it joins property_set_members (a fewo-side concept). New files (all in oatpp-authkit): - dto/UserPermissionDto.hpp — UserPropertyPermissionDto + UserGroupPermissionDto, both registered as temporal. EffectivePermissionDto stays in fewo (it's the result shape of fewo's property_set_members JOIN). - db/UserPermissionDb.hpp — DbClient with CRUD for both tables. Each table also has a *Schema struct exposing kSchema for SchemaBuilder composition. Natural-key UNIQUE indexes carried explicitly: ux_..._user_property_until, ux_..._user_set_until. - repo/ConcreteUserPermissionRepository.hpp — two concrete repos + makeUserPropertyPermissionRepository / makeUserGroupPermissionRepository factories that wrap each in TemporalRepository. - test/test_user_permission_schema.cpp — verifies both schemas compose with TemporalRepository to produce the expected 5 DDL statements each (entity table + 3 schema indexes + 1 temporal composite index). 12 of 12 tests pass. Bumped 0.10.0 → 0.11.0. Per-row natural-key UNIQUE prevents duplicate live grants for the same (user_id, property_id) or (user_id, set_id) pair while still allowing historical rows for the same key (their valid_until differs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
178 lines
6.8 KiB
C++
178 lines
6.8 KiB
C++
#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<oatpp::orm::Executor>& 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<dto::UserPropertyPermissionDto>, 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<dto::UserGroupPermissionDto>, 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<UserPropertyPermissionDto>` 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
|