持久化码后台操作记录
新增兑换码和邀请码后台操作持久表。 列表接口返回全部操作记录并同步前后端契约。 后台页面展示持久化操作历史并移除最近一条会话记录。 更新 SpacetimeDB 迁移表清单、生成绑定和后端架构文档。
This commit is contained in:
@@ -416,8 +416,18 @@ export interface ProfileRedeemCodeAdminResponse {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ProfileCodeOperationAdminResponse {
|
||||
operationId: string;
|
||||
codeKind: 'redeem' | 'invite' | string;
|
||||
code: string;
|
||||
action: 'create' | 'update' | 'disable' | string;
|
||||
operatorUserId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface ProfileRedeemCodeAdminListResponse {
|
||||
entries: ProfileRedeemCodeAdminResponse[];
|
||||
operations: ProfileCodeOperationAdminResponse[];
|
||||
}
|
||||
|
||||
export interface ProfileInviteCodeAdminResponse {
|
||||
@@ -433,6 +443,7 @@ export interface ProfileInviteCodeAdminResponse {
|
||||
|
||||
export interface ProfileInviteCodeAdminListResponse {
|
||||
entries: ProfileInviteCodeAdminResponse[];
|
||||
operations: ProfileCodeOperationAdminResponse[];
|
||||
}
|
||||
|
||||
export interface ProfileTaskConfigAdminResponse {
|
||||
|
||||
@@ -8,9 +8,7 @@ import {
|
||||
} from '../api/adminApiClient';
|
||||
import type {
|
||||
AdminSessionPayload,
|
||||
ProfileInviteCodeAdminResponse,
|
||||
ProfileRechargeProductConfigAdminResponse,
|
||||
ProfileRedeemCodeAdminResponse,
|
||||
ProfileTaskConfigAdminResponse,
|
||||
ProfileWalletConfigAdminResponse,
|
||||
} from '../api/adminApiTypes';
|
||||
@@ -47,11 +45,6 @@ export function AdminApp() {
|
||||
resolveAdminRoute(window.location.hash),
|
||||
);
|
||||
const [loginNotice, setLoginNotice] = useState('');
|
||||
// 兑换码页会随页签切换卸载,最近操作记录需要放在会话层保留。
|
||||
const [redeemResult, setRedeemResult] =
|
||||
useState<ProfileRedeemCodeAdminResponse | null>(null);
|
||||
const [inviteResult, setInviteResult] =
|
||||
useState<ProfileInviteCodeAdminResponse | null>(null);
|
||||
const [taskConfigResult, setTaskConfigResult] =
|
||||
useState<ProfileTaskConfigAdminResponse | null>(null);
|
||||
const [profileWalletConfigResult, setProfileWalletConfigResult] =
|
||||
@@ -63,8 +56,6 @@ export function AdminApp() {
|
||||
clearStoredAdminToken();
|
||||
setToken('');
|
||||
setAdmin(null);
|
||||
setRedeemResult(null);
|
||||
setInviteResult(null);
|
||||
setTaskConfigResult(null);
|
||||
setProfileWalletConfigResult(null);
|
||||
setRechargeProductResult(null);
|
||||
@@ -134,8 +125,6 @@ export function AdminApp() {
|
||||
setStoredAdminToken(response.token);
|
||||
setToken(response.token);
|
||||
setAdmin(response.admin);
|
||||
setRedeemResult(null);
|
||||
setInviteResult(null);
|
||||
setTaskConfigResult(null);
|
||||
setProfileWalletConfigResult(null);
|
||||
setRechargeProductResult(null);
|
||||
@@ -197,18 +186,14 @@ export function AdminApp() {
|
||||
) : null}
|
||||
{routeId === 'redeem' ? (
|
||||
<AdminRedeemCodePage
|
||||
result={redeemResult}
|
||||
token={token}
|
||||
onUnauthorized={handleUnauthorized}
|
||||
onResultChange={setRedeemResult}
|
||||
/>
|
||||
) : null}
|
||||
{routeId === 'invite' ? (
|
||||
<AdminInviteCodePage
|
||||
result={inviteResult}
|
||||
token={token}
|
||||
onUnauthorized={handleUnauthorized}
|
||||
onResultChange={setInviteResult}
|
||||
/>
|
||||
) : null}
|
||||
{routeId === 'creation-announcement' ? (
|
||||
|
||||
@@ -703,6 +703,8 @@ const databaseTableColumnLabelMap: Record<string, string> = {
|
||||
event_id: '事件ID',
|
||||
event_key: '事件键',
|
||||
event_title: '事件名称',
|
||||
operation_id: '操作ID',
|
||||
code_kind: '码类型',
|
||||
scope_kind: '范围类型',
|
||||
scope_id: '范围ID',
|
||||
day_key: '日期键',
|
||||
@@ -811,6 +813,7 @@ const databaseTableColumnLabelMap: Record<string, string> = {
|
||||
record_id: '记录ID',
|
||||
created_by: '创建人',
|
||||
updated_by: '更新人',
|
||||
operator_user_id: '操作人ID',
|
||||
total_count: '总数',
|
||||
max_uses: '最大使用次数',
|
||||
global_used_count: '全局使用次数',
|
||||
@@ -837,6 +840,8 @@ const databaseTableColumnDescriptionMap: Record<string, string> = {
|
||||
event_id: '当前埋点事件的唯一标识',
|
||||
event_key: '埋点事件键',
|
||||
event_title: '埋点事件展示名称',
|
||||
operation_id: '后台操作记录的唯一标识',
|
||||
code_kind: '码类型,redeem 表示兑换码,invite 表示邀请码',
|
||||
scope_kind: '埋点统计范围类型',
|
||||
scope_id: '埋点统计范围标识',
|
||||
day_key: '按天聚合时使用的日期键',
|
||||
@@ -943,6 +948,7 @@ const databaseTableColumnDescriptionMap: Record<string, string> = {
|
||||
record_id: '记录标识',
|
||||
created_by: '创建该记录的主体',
|
||||
updated_by: '最后更新该记录的主体',
|
||||
operator_user_id: '执行后台操作的用户标识',
|
||||
total_count: '累计总数',
|
||||
max_uses: '允许的最大使用次数',
|
||||
global_used_count: '当前已使用次数',
|
||||
@@ -1188,6 +1194,7 @@ const databaseTableLabelMap: Record<string, string> = {
|
||||
profile_task_reward_claim: '个人任务领奖',
|
||||
profile_redeem_code: '兑换码',
|
||||
profile_redeem_code_usage: '兑换码使用记录',
|
||||
profile_code_operation: '码操作记录',
|
||||
profile_invite_code: '邀请码',
|
||||
profile_referral_relation: '邀请关系',
|
||||
profile_played_world: '已玩世界',
|
||||
@@ -1269,6 +1276,7 @@ const databaseTableDescriptionMap: Record<string, string> = {
|
||||
profile_task_reward_claim: '个人任务领奖记录表',
|
||||
profile_redeem_code: '运营兑换码表',
|
||||
profile_redeem_code_usage: '兑换码使用记录表',
|
||||
profile_code_operation: '兑换码/邀请码后台操作记录表',
|
||||
profile_invite_code: '用户邀请中心邀请码表',
|
||||
profile_referral_relation: '邀请关系记录表',
|
||||
profile_played_world: '用户已玩世界记录表',
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../api/adminApiClient';
|
||||
import type {
|
||||
AdminUpsertProfileInviteCodeRequest,
|
||||
ProfileCodeOperationAdminResponse,
|
||||
ProfileInviteCodeAdminResponse,
|
||||
} from '../api/adminApiTypes';
|
||||
import {useAdminWriteConfirm} from '../components/useAdminWriteConfirm';
|
||||
@@ -14,16 +15,12 @@ import {handlePageError} from './pageUtils';
|
||||
|
||||
interface AdminInviteCodePageProps {
|
||||
token: string;
|
||||
result: ProfileInviteCodeAdminResponse | null;
|
||||
onUnauthorized: (message?: string) => void;
|
||||
onResultChange: (result: ProfileInviteCodeAdminResponse) => void;
|
||||
}
|
||||
|
||||
export function AdminInviteCodePage({
|
||||
token,
|
||||
result,
|
||||
onUnauthorized,
|
||||
onResultChange,
|
||||
}: AdminInviteCodePageProps) {
|
||||
const [inviteCode, setInviteCode] = useState('');
|
||||
const [startsAt, setStartsAt] = useState('');
|
||||
@@ -33,6 +30,7 @@ export function AdminInviteCodePage({
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [listErrorMessage, setListErrorMessage] = useState('');
|
||||
const [entries, setEntries] = useState<ProfileInviteCodeAdminResponse[]>([]);
|
||||
const [operations, setOperations] = useState<ProfileCodeOperationAdminResponse[]>([]);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const {confirmWrite, confirmDialog} = useAdminWriteConfirm();
|
||||
@@ -48,6 +46,7 @@ export function AdminInviteCodePage({
|
||||
try {
|
||||
const response = await listProfileInviteCodes(token);
|
||||
setEntries(response.entries);
|
||||
setOperations(response.operations ?? []);
|
||||
} catch (error: unknown) {
|
||||
handlePageError(error, onUnauthorized, setListErrorMessage);
|
||||
} finally {
|
||||
@@ -89,9 +88,8 @@ export function AdminInviteCodePage({
|
||||
expiresAt: expiresAt ? toIsoDateTime(expiresAt) : null,
|
||||
};
|
||||
const response = await upsertProfileInviteCode(token, payload);
|
||||
onResultChange(response);
|
||||
upsertEntry(response);
|
||||
fillForm(response);
|
||||
await refreshInviteCodes();
|
||||
} catch (error: unknown) {
|
||||
handlePageError(error, onUnauthorized, setErrorMessage);
|
||||
} finally {
|
||||
@@ -99,23 +97,6 @@ export function AdminInviteCodePage({
|
||||
}
|
||||
}
|
||||
|
||||
function upsertEntry(next: ProfileInviteCodeAdminResponse) {
|
||||
setEntries((current) => {
|
||||
const rest = current.filter((entry) => entry.inviteCode !== next.inviteCode);
|
||||
return [...rest, next].sort((left, right) => {
|
||||
const leftUpdatedAt = Date.parse(left.updatedAt);
|
||||
const rightUpdatedAt = Date.parse(right.updatedAt);
|
||||
if (Number.isFinite(leftUpdatedAt) && Number.isFinite(rightUpdatedAt)) {
|
||||
const updatedCompare = rightUpdatedAt - leftUpdatedAt;
|
||||
if (updatedCompare !== 0) {
|
||||
return updatedCompare;
|
||||
}
|
||||
}
|
||||
return left.inviteCode.localeCompare(right.inviteCode);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function fillForm(entry: ProfileInviteCodeAdminResponse) {
|
||||
setInviteCode(entry.inviteCode);
|
||||
setStartsAt(toDateTimeLocalValue(entry.startsAt));
|
||||
@@ -278,42 +259,32 @@ export function AdminInviteCodePage({
|
||||
|
||||
<section className="admin-panel admin-result-panel">
|
||||
<div className="admin-panel-heading">
|
||||
<h3>记录</h3>
|
||||
<span>{result?.inviteCode ?? '-'}</span>
|
||||
<h3>操作记录</h3>
|
||||
<span>{operations.length}</span>
|
||||
</div>
|
||||
{result ? (
|
||||
<dl className="admin-info-list">
|
||||
<div>
|
||||
<dt>邀请码</dt>
|
||||
<dd>{result.inviteCode}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>有效期</dt>
|
||||
<dd>{formatValidityWindow(result)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>标签</dt>
|
||||
<dd>
|
||||
<TagList tags={metadataUserTags(result.metadata)} />
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>创建</dt>
|
||||
<dd>{result.createdAt}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>更新</dt>
|
||||
<dd>{result.updatedAt}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Metadata</dt>
|
||||
<dd>
|
||||
<pre className="admin-code-block">
|
||||
{JSON.stringify(result.metadata, null, 2)}
|
||||
</pre>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{operations.length ? (
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table admin-table-compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>操作</th>
|
||||
<th>邀请码</th>
|
||||
<th>操作人</th>
|
||||
<th>时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{operations.map((operation) => (
|
||||
<tr key={operation.operationId}>
|
||||
<td>{operationActionLabel(operation.action)}</td>
|
||||
<td>{operation.code}</td>
|
||||
<td>{operation.operatorUserId}</td>
|
||||
<td>{formatDateTime(operation.createdAt)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="admin-empty-state">暂无记录</div>
|
||||
)}
|
||||
@@ -475,6 +446,19 @@ function formatDateTime(value: string) {
|
||||
return date.toLocaleString('zh-CN', {hour12: false});
|
||||
}
|
||||
|
||||
function operationActionLabel(action: string) {
|
||||
if (action === 'create') {
|
||||
return '新增';
|
||||
}
|
||||
if (action === 'update') {
|
||||
return '更新';
|
||||
}
|
||||
if (action === 'disable') {
|
||||
return '停用';
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
upsertProfileRedeemCode,
|
||||
} from '../api/adminApiClient';
|
||||
import type {
|
||||
ProfileCodeOperationAdminResponse,
|
||||
ProfileRedeemCodeAdminResponse,
|
||||
ProfileRedeemCodeMode,
|
||||
} from '../api/adminApiTypes';
|
||||
@@ -15,9 +16,7 @@ import {handlePageError, splitLines} from './pageUtils';
|
||||
|
||||
interface AdminRedeemCodePageProps {
|
||||
token: string;
|
||||
result: ProfileRedeemCodeAdminResponse | null;
|
||||
onUnauthorized: (message?: string) => void;
|
||||
onResultChange: (result: ProfileRedeemCodeAdminResponse) => void;
|
||||
}
|
||||
|
||||
const redeemModes: Array<{value: ProfileRedeemCodeMode; label: string}> = [
|
||||
@@ -28,9 +27,7 @@ const redeemModes: Array<{value: ProfileRedeemCodeMode; label: string}> = [
|
||||
|
||||
export function AdminRedeemCodePage({
|
||||
token,
|
||||
result,
|
||||
onUnauthorized,
|
||||
onResultChange,
|
||||
}: AdminRedeemCodePageProps) {
|
||||
const [code, setCode] = useState('');
|
||||
const [mode, setMode] = useState<ProfileRedeemCodeMode>('public');
|
||||
@@ -44,6 +41,7 @@ export function AdminRedeemCodePage({
|
||||
const [disableErrorMessage, setDisableErrorMessage] = useState('');
|
||||
const [listErrorMessage, setListErrorMessage] = useState('');
|
||||
const [entries, setEntries] = useState<ProfileRedeemCodeAdminResponse[]>([]);
|
||||
const [operations, setOperations] = useState<ProfileCodeOperationAdminResponse[]>([]);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isDisabling, setIsDisabling] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -60,6 +58,7 @@ export function AdminRedeemCodePage({
|
||||
try {
|
||||
const response = await listProfileRedeemCodes(token);
|
||||
setEntries(response.entries);
|
||||
setOperations(response.operations ?? []);
|
||||
} catch (error: unknown) {
|
||||
handlePageError(error, onUnauthorized, setListErrorMessage);
|
||||
} finally {
|
||||
@@ -94,9 +93,8 @@ export function AdminRedeemCodePage({
|
||||
allowedPublicUserCodes:
|
||||
mode === 'private' ? splitLines(allowedPublicUserCodes) : [],
|
||||
});
|
||||
onResultChange(response);
|
||||
upsertEntry(response);
|
||||
fillForm(response);
|
||||
await refreshRedeemCodes();
|
||||
} catch (error: unknown) {
|
||||
handlePageError(error, onUnauthorized, setErrorMessage);
|
||||
} finally {
|
||||
@@ -124,9 +122,8 @@ export function AdminRedeemCodePage({
|
||||
const response = await disableProfileRedeemCode(token, {
|
||||
code: disableCode.trim(),
|
||||
});
|
||||
onResultChange(response);
|
||||
upsertEntry(response);
|
||||
fillForm(response);
|
||||
await refreshRedeemCodes();
|
||||
} catch (error: unknown) {
|
||||
handlePageError(error, onUnauthorized, setDisableErrorMessage);
|
||||
} finally {
|
||||
@@ -134,23 +131,6 @@ export function AdminRedeemCodePage({
|
||||
}
|
||||
}
|
||||
|
||||
function upsertEntry(next: ProfileRedeemCodeAdminResponse) {
|
||||
setEntries((current) => {
|
||||
const rest = current.filter((entry) => entry.code !== next.code);
|
||||
return [...rest, next].sort((left, right) => {
|
||||
const leftUpdatedAt = Date.parse(left.updatedAt);
|
||||
const rightUpdatedAt = Date.parse(right.updatedAt);
|
||||
if (Number.isFinite(leftUpdatedAt) && Number.isFinite(rightUpdatedAt)) {
|
||||
const updatedCompare = rightUpdatedAt - leftUpdatedAt;
|
||||
if (updatedCompare !== 0) {
|
||||
return updatedCompare;
|
||||
}
|
||||
}
|
||||
return left.code.localeCompare(right.code);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function fillForm(entry: ProfileRedeemCodeAdminResponse) {
|
||||
setCode(entry.code);
|
||||
setMode(entry.mode);
|
||||
@@ -354,40 +334,32 @@ export function AdminRedeemCodePage({
|
||||
|
||||
<section className="admin-panel admin-result-panel">
|
||||
<div className="admin-panel-heading">
|
||||
<h3>记录</h3>
|
||||
<span>{result?.mode ?? '-'}</span>
|
||||
<h3>操作记录</h3>
|
||||
<span>{operations.length}</span>
|
||||
</div>
|
||||
{result ? (
|
||||
<dl className="admin-info-list">
|
||||
<div>
|
||||
<dt>Code</dt>
|
||||
<dd>{result.code}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>奖励</dt>
|
||||
<dd>{result.rewardPoints}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>最大次数</dt>
|
||||
<dd>{result.maxUses}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>全局已用</dt>
|
||||
<dd>{result.globalUsedCount}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>状态</dt>
|
||||
<dd>{result.enabled ? '启用' : '停用'}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>创建人</dt>
|
||||
<dd>{result.createdBy}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>更新</dt>
|
||||
<dd>{result.updatedAt}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{operations.length ? (
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table admin-table-compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>操作</th>
|
||||
<th>Code</th>
|
||||
<th>操作人</th>
|
||||
<th>时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{operations.map((operation) => (
|
||||
<tr key={operation.operationId}>
|
||||
<td>{operationActionLabel(operation.action)}</td>
|
||||
<td>{operation.code}</td>
|
||||
<td>{operation.operatorUserId}</td>
|
||||
<td>{formatDateTime(operation.createdAt)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="admin-empty-state">暂无记录</div>
|
||||
)}
|
||||
@@ -407,3 +379,24 @@ function parsePositiveInteger(value: string) {
|
||||
function redeemModeLabel(value: ProfileRedeemCodeMode) {
|
||||
return redeemModes.find((item) => item.value === value)?.label ?? value;
|
||||
}
|
||||
|
||||
function operationActionLabel(action: string) {
|
||||
if (action === 'create') {
|
||||
return '新增';
|
||||
}
|
||||
if (action === 'update') {
|
||||
return '更新';
|
||||
}
|
||||
if (action === 'disable') {
|
||||
return '停用';
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
function formatDateTime(value: string) {
|
||||
const date = new Date(value);
|
||||
if (!Number.isFinite(date.getTime())) {
|
||||
return value;
|
||||
}
|
||||
return date.toLocaleString('zh-CN', {hour12: false});
|
||||
}
|
||||
|
||||
@@ -620,6 +620,12 @@ npm run check:server-rs-ddd
|
||||
- Rust 结构体:`ProfileInviteCode`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
||||
|
||||
### `profile_code_operation`
|
||||
|
||||
- Rust 结构体:`ProfileCodeOperation`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
||||
- 作用:后台兑换码 / 邀请码的持久操作记录,记录新增、更新、停用的码值、操作人和操作时间。
|
||||
|
||||
### `profile_membership`
|
||||
|
||||
- Rust 结构体:`ProfileMembership`
|
||||
|
||||
@@ -409,8 +409,18 @@ export type ProfileRedeemCodeAdminResponse = {
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type ProfileCodeOperationAdminResponse = {
|
||||
operationId: string;
|
||||
codeKind: 'redeem' | 'invite' | string;
|
||||
code: string;
|
||||
action: 'create' | 'update' | 'disable' | string;
|
||||
operatorUserId: string;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type ProfileRedeemCodeAdminListResponse = {
|
||||
entries: ProfileRedeemCodeAdminResponse[];
|
||||
operations: ProfileCodeOperationAdminResponse[];
|
||||
};
|
||||
|
||||
export type AdminUpsertProfileRedeemCodeRequest = {
|
||||
@@ -447,6 +457,7 @@ export type ProfileInviteCodeAdminResponse = {
|
||||
|
||||
export type ProfileInviteCodeAdminListResponse = {
|
||||
entries: ProfileInviteCodeAdminResponse[];
|
||||
operations: ProfileCodeOperationAdminResponse[];
|
||||
};
|
||||
|
||||
export type ProfilePlayedWorkSummary = {
|
||||
|
||||
@@ -14,8 +14,9 @@ use module_runtime::{
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM,
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM_VIRTUAL,
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_NATIVE, RuntimeProfileFeedbackEvidenceRecord,
|
||||
RuntimeProfileFeedbackEvidenceSnapshot, RuntimeProfileFeedbackSubmissionRecord,
|
||||
RuntimeProfileInviteCodeRecord, RuntimeProfileMembershipBenefitRecord,
|
||||
RuntimeProfileCodeOperationRecord, RuntimeProfileFeedbackEvidenceSnapshot,
|
||||
RuntimeProfileFeedbackSubmissionRecord, RuntimeProfileInviteCodeRecord,
|
||||
RuntimeProfileMembershipBenefitRecord,
|
||||
RuntimeProfileMembershipTier, RuntimeProfileRechargeCenterRecord,
|
||||
RuntimeProfileRechargeOrderRecord, RuntimeProfileRechargeOrderStatus,
|
||||
RuntimeProfileRechargeProductConfigRecord, RuntimeProfileRechargeProductKind,
|
||||
@@ -53,8 +54,9 @@ use shared_contracts::runtime::{
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_PUZZLE_AUTHOR_INCENTIVE_CLAIM,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_REDEEM_CODE_REWARD,
|
||||
PROFILE_WALLET_LEDGER_SOURCE_TYPE_SNAPSHOT_SYNC, ProfileDashboardSummaryResponse,
|
||||
ProfileFeedbackEvidenceItemResponse, ProfileFeedbackSubmissionResponse,
|
||||
ProfileInviteCodeAdminListResponse, ProfileInviteCodeAdminResponse,
|
||||
ProfileCodeOperationAdminResponse, ProfileFeedbackEvidenceItemResponse,
|
||||
ProfileFeedbackSubmissionResponse, ProfileInviteCodeAdminListResponse,
|
||||
ProfileInviteCodeAdminResponse,
|
||||
ProfileMembershipBenefitResponse, ProfileMembershipResponse, ProfilePlayStatsResponse,
|
||||
ProfilePlayedWorkSummaryResponse, ProfileRechargeCenterResponse, ProfileRechargeOrderResponse,
|
||||
ProfileRechargeProductConfigAdminListResponse, ProfileRechargeProductConfigAdminResponse,
|
||||
@@ -959,7 +961,7 @@ pub async fn admin_list_profile_redeem_codes(
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let entries = state
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.admin_list_profile_redeem_codes(admin.session().subject.clone())
|
||||
.await
|
||||
@@ -973,10 +975,16 @@ pub async fn admin_list_profile_redeem_codes(
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
ProfileRedeemCodeAdminListResponse {
|
||||
entries: entries
|
||||
entries: record
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(build_profile_redeem_code_admin_response)
|
||||
.collect(),
|
||||
operations: record
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(build_profile_code_operation_admin_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -1054,7 +1062,7 @@ pub async fn admin_list_profile_invite_codes(
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(admin): Extension<AuthenticatedAdmin>,
|
||||
) -> Result<Json<Value>, Response> {
|
||||
let entries = state
|
||||
let record = state
|
||||
.spacetime_client()
|
||||
.admin_list_profile_invite_codes(admin.session().subject.clone())
|
||||
.await
|
||||
@@ -1068,10 +1076,16 @@ pub async fn admin_list_profile_invite_codes(
|
||||
Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
ProfileInviteCodeAdminListResponse {
|
||||
entries: entries
|
||||
entries: record
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(build_profile_invite_code_admin_response)
|
||||
.collect(),
|
||||
operations: record
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(build_profile_code_operation_admin_response)
|
||||
.collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -2018,6 +2032,19 @@ fn build_profile_redeem_code_admin_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_code_operation_admin_response(
|
||||
record: RuntimeProfileCodeOperationRecord,
|
||||
) -> ProfileCodeOperationAdminResponse {
|
||||
ProfileCodeOperationAdminResponse {
|
||||
operation_id: record.operation_id,
|
||||
code_kind: record.code_kind,
|
||||
code: record.code,
|
||||
action: record.action,
|
||||
operator_user_id: record.operator_user_id,
|
||||
created_at: record.created_at,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use module_auth::{ResolveWechatLoginInput, WechatIdentityProfile};
|
||||
|
||||
@@ -1525,6 +1525,20 @@ pub fn build_runtime_profile_redeem_code_record(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_code_operation_record(
|
||||
snapshot: RuntimeProfileCodeOperationSnapshot,
|
||||
) -> RuntimeProfileCodeOperationRecord {
|
||||
RuntimeProfileCodeOperationRecord {
|
||||
operation_id: snapshot.operation_id,
|
||||
code_kind: snapshot.code_kind,
|
||||
code: snapshot.code,
|
||||
action: snapshot.action,
|
||||
operator_user_id: snapshot.operator_user_id,
|
||||
created_at: format_utc_micros(snapshot.created_at_micros),
|
||||
created_at_micros: snapshot.created_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_invite_code_record(
|
||||
snapshot: RuntimeProfileInviteCodeSnapshot,
|
||||
) -> RuntimeProfileInviteCodeRecord {
|
||||
|
||||
@@ -1425,6 +1425,17 @@ pub struct RuntimeProfileRedeemCodeSnapshot {
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileCodeOperationSnapshot {
|
||||
pub operation_id: String,
|
||||
pub code_kind: String,
|
||||
pub code: String,
|
||||
pub action: String,
|
||||
pub operator_user_id: String,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRedeemCodeAdminProcedureResult {
|
||||
@@ -1438,6 +1449,7 @@ pub struct RuntimeProfileRedeemCodeAdminProcedureResult {
|
||||
pub struct RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileRedeemCodeSnapshot>,
|
||||
pub operations: Vec<RuntimeProfileCodeOperationSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1483,6 +1495,7 @@ pub struct RuntimeProfileInviteCodeAdminProcedureResult {
|
||||
pub struct RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileInviteCodeSnapshot>,
|
||||
pub operations: Vec<RuntimeProfileCodeOperationSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1867,6 +1880,29 @@ pub struct RuntimeProfileRedeemCodeRecord {
|
||||
pub updated_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileCodeOperationRecord {
|
||||
pub operation_id: String,
|
||||
pub code_kind: String,
|
||||
pub code: String,
|
||||
pub action: String,
|
||||
pub operator_user_id: String,
|
||||
pub created_at: String,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileRedeemCodeAdminListRecord {
|
||||
pub entries: Vec<RuntimeProfileRedeemCodeRecord>,
|
||||
pub operations: Vec<RuntimeProfileCodeOperationRecord>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileInviteCodeAdminListRecord {
|
||||
pub entries: Vec<RuntimeProfileInviteCodeRecord>,
|
||||
pub operations: Vec<RuntimeProfileCodeOperationRecord>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RuntimeProfileInviteCodeRecord {
|
||||
pub user_id: String,
|
||||
|
||||
@@ -648,10 +648,22 @@ pub struct ProfileRedeemCodeAdminResponse {
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileCodeOperationAdminResponse {
|
||||
pub operation_id: String,
|
||||
pub code_kind: String,
|
||||
pub code: String,
|
||||
pub action: String,
|
||||
pub operator_user_id: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileRedeemCodeAdminListResponse {
|
||||
pub entries: Vec<ProfileRedeemCodeAdminResponse>,
|
||||
pub operations: Vec<ProfileCodeOperationAdminResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -671,6 +683,7 @@ pub struct ProfileInviteCodeAdminResponse {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProfileInviteCodeAdminListResponse {
|
||||
pub entries: Vec<ProfileInviteCodeAdminResponse>,
|
||||
pub operations: Vec<ProfileCodeOperationAdminResponse>,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
|
||||
@@ -199,9 +199,11 @@ use module_npc::{
|
||||
use module_runtime::{
|
||||
AnalyticsMetricQueryResponse as DomainAnalyticsMetricQueryResponse, RuntimeBrowseHistoryRecord,
|
||||
RuntimePlatformTheme as DomainRuntimePlatformTheme, RuntimeProfileDashboardRecord,
|
||||
RuntimeProfileFeedbackSubmissionRecord, RuntimeProfileInviteCodeRecord,
|
||||
RuntimeProfilePlayStatsRecord, RuntimeProfileRechargeCenterRecord,
|
||||
RuntimeProfileRechargeOrderRecord, RuntimeProfileRechargeProductConfigRecord,
|
||||
RuntimeProfileFeedbackSubmissionRecord, RuntimeProfileInviteCodeAdminListRecord,
|
||||
RuntimeProfileInviteCodeRecord, RuntimeProfilePlayStatsRecord,
|
||||
RuntimeProfileRechargeCenterRecord, RuntimeProfileRechargeOrderRecord,
|
||||
RuntimeProfileRechargeProductConfigRecord,
|
||||
RuntimeProfileRedeemCodeAdminListRecord,
|
||||
RuntimeProfileRedeemCodeMode as DomainRuntimeProfileRedeemCodeMode,
|
||||
RuntimeProfileRedeemCodeRecord, RuntimeProfileRewardCodeRedeemRecord,
|
||||
RuntimeProfileSaveArchiveRecord, RuntimeProfileTaskCenterRecord, RuntimeProfileTaskClaimRecord,
|
||||
@@ -213,6 +215,7 @@ use module_runtime::{
|
||||
build_runtime_browse_history_clear_input, build_runtime_browse_history_list_input,
|
||||
build_runtime_browse_history_record, build_runtime_browse_history_sync_input,
|
||||
build_runtime_profile_dashboard_get_input, build_runtime_profile_dashboard_record,
|
||||
build_runtime_profile_code_operation_record,
|
||||
build_runtime_profile_feedback_submission_input,
|
||||
build_runtime_profile_feedback_submission_record,
|
||||
build_runtime_profile_invite_code_admin_list_input,
|
||||
|
||||
@@ -668,12 +668,13 @@ pub(crate) fn map_runtime_profile_redeem_code_admin_procedure_result(
|
||||
|
||||
pub(crate) fn map_runtime_profile_redeem_code_admin_list_procedure_result(
|
||||
result: RuntimeProfileRedeemCodeAdminListProcedureResult,
|
||||
) -> Result<Vec<RuntimeProfileRedeemCodeRecord>, SpacetimeClientError> {
|
||||
) -> Result<RuntimeProfileRedeemCodeAdminListRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
Ok(result
|
||||
Ok(RuntimeProfileRedeemCodeAdminListRecord {
|
||||
entries: result
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
@@ -681,7 +682,17 @@ pub(crate) fn map_runtime_profile_redeem_code_admin_list_procedure_result(
|
||||
snapshot,
|
||||
))
|
||||
})
|
||||
.collect())
|
||||
.collect(),
|
||||
operations: result
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
build_runtime_profile_code_operation_record(
|
||||
map_runtime_profile_code_operation_snapshot(snapshot),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_invite_code_admin_procedure_result(
|
||||
@@ -706,12 +717,13 @@ pub(crate) fn map_runtime_profile_invite_code_admin_procedure_result(
|
||||
|
||||
pub(crate) fn map_runtime_profile_invite_code_admin_list_procedure_result(
|
||||
result: RuntimeProfileInviteCodeAdminListProcedureResult,
|
||||
) -> Result<Vec<RuntimeProfileInviteCodeRecord>, SpacetimeClientError> {
|
||||
) -> Result<RuntimeProfileInviteCodeAdminListRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
Ok(result
|
||||
Ok(RuntimeProfileInviteCodeAdminListRecord {
|
||||
entries: result
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
@@ -719,7 +731,17 @@ pub(crate) fn map_runtime_profile_invite_code_admin_list_procedure_result(
|
||||
snapshot,
|
||||
))
|
||||
})
|
||||
.collect())
|
||||
.collect(),
|
||||
operations: result
|
||||
.operations
|
||||
.into_iter()
|
||||
.map(|snapshot| {
|
||||
build_runtime_profile_code_operation_record(
|
||||
map_runtime_profile_code_operation_snapshot(snapshot),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_play_stats_procedure_result(
|
||||
@@ -1112,6 +1134,19 @@ pub(crate) fn map_runtime_profile_invite_code_snapshot(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_code_operation_snapshot(
|
||||
snapshot: RuntimeProfileCodeOperationSnapshot,
|
||||
) -> module_runtime::RuntimeProfileCodeOperationSnapshot {
|
||||
module_runtime::RuntimeProfileCodeOperationSnapshot {
|
||||
operation_id: snapshot.operation_id,
|
||||
code_kind: snapshot.code_kind,
|
||||
code: snapshot.code,
|
||||
action: snapshot.action,
|
||||
operator_user_id: snapshot.operator_user_id,
|
||||
created_at_micros: snapshot.created_at_micros,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_played_world_snapshot(
|
||||
snapshot: RuntimeProfilePlayedWorldSnapshot,
|
||||
) -> module_runtime::RuntimeProfilePlayedWorldSnapshot {
|
||||
|
||||
@@ -653,6 +653,8 @@ pub mod player_progression_procedure_result_type;
|
||||
pub mod player_progression_snapshot_type;
|
||||
pub mod player_progression_table;
|
||||
pub mod player_progression_type;
|
||||
pub mod profile_code_operation_table;
|
||||
pub mod profile_code_operation_type;
|
||||
pub mod profile_dashboard_state_table;
|
||||
pub mod profile_dashboard_state_type;
|
||||
pub mod profile_feedback_submission_table;
|
||||
@@ -921,6 +923,7 @@ pub mod runtime_item_equipment_slot_type;
|
||||
pub mod runtime_item_reward_item_rarity_type;
|
||||
pub mod runtime_item_reward_item_snapshot_type;
|
||||
pub mod runtime_platform_theme_type;
|
||||
pub mod runtime_profile_code_operation_snapshot_type;
|
||||
pub mod runtime_profile_dashboard_get_input_type;
|
||||
pub mod runtime_profile_dashboard_procedure_result_type;
|
||||
pub mod runtime_profile_dashboard_snapshot_type;
|
||||
@@ -1881,6 +1884,8 @@ pub use player_progression_procedure_result_type::PlayerProgressionProcedureResu
|
||||
pub use player_progression_snapshot_type::PlayerProgressionSnapshot;
|
||||
pub use player_progression_table::*;
|
||||
pub use player_progression_type::PlayerProgression;
|
||||
pub use profile_code_operation_table::*;
|
||||
pub use profile_code_operation_type::ProfileCodeOperation;
|
||||
pub use profile_dashboard_state_table::*;
|
||||
pub use profile_dashboard_state_type::ProfileDashboardState;
|
||||
pub use profile_feedback_submission_table::*;
|
||||
@@ -2149,6 +2154,7 @@ pub use runtime_item_equipment_slot_type::RuntimeItemEquipmentSlot;
|
||||
pub use runtime_item_reward_item_rarity_type::RuntimeItemRewardItemRarity;
|
||||
pub use runtime_item_reward_item_snapshot_type::RuntimeItemRewardItemSnapshot;
|
||||
pub use runtime_platform_theme_type::RuntimePlatformTheme;
|
||||
pub use runtime_profile_code_operation_snapshot_type::RuntimeProfileCodeOperationSnapshot;
|
||||
pub use runtime_profile_dashboard_get_input_type::RuntimeProfileDashboardGetInput;
|
||||
pub use runtime_profile_dashboard_procedure_result_type::RuntimeProfileDashboardProcedureResult;
|
||||
pub use runtime_profile_dashboard_snapshot_type::RuntimeProfileDashboardSnapshot;
|
||||
@@ -2795,6 +2801,7 @@ pub struct DbUpdate {
|
||||
match_3_d_work_profile: __sdk::TableUpdate<Match3DWorkProfileRow>,
|
||||
npc_state: __sdk::TableUpdate<NpcState>,
|
||||
player_progression: __sdk::TableUpdate<PlayerProgression>,
|
||||
profile_code_operation: __sdk::TableUpdate<ProfileCodeOperation>,
|
||||
profile_dashboard_state: __sdk::TableUpdate<ProfileDashboardState>,
|
||||
profile_feedback_submission: __sdk::TableUpdate<ProfileFeedbackSubmission>,
|
||||
profile_invite_code: __sdk::TableUpdate<ProfileInviteCode>,
|
||||
@@ -3059,6 +3066,9 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
|
||||
"player_progression" => db_update
|
||||
.player_progression
|
||||
.append(player_progression_table::parse_table_update(table_update)?),
|
||||
"profile_code_operation" => db_update.profile_code_operation.append(
|
||||
profile_code_operation_table::parse_table_update(table_update)?,
|
||||
),
|
||||
"profile_dashboard_state" => db_update.profile_dashboard_state.append(
|
||||
profile_dashboard_state_table::parse_table_update(table_update)?,
|
||||
),
|
||||
@@ -3570,6 +3580,12 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
&self.player_progression,
|
||||
)
|
||||
.with_updates_by_pk(|row| &row.user_id);
|
||||
diff.profile_code_operation = cache
|
||||
.apply_diff_to_table::<ProfileCodeOperation>(
|
||||
"profile_code_operation",
|
||||
&self.profile_code_operation,
|
||||
)
|
||||
.with_updates_by_pk(|row| &row.operation_id);
|
||||
diff.profile_dashboard_state = cache
|
||||
.apply_diff_to_table::<ProfileDashboardState>(
|
||||
"profile_dashboard_state",
|
||||
@@ -4106,6 +4122,9 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
"player_progression" => db_update
|
||||
.player_progression
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_code_operation" => db_update
|
||||
.profile_code_operation
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_dashboard_state" => db_update
|
||||
.profile_dashboard_state
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
@@ -4497,6 +4516,9 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
"player_progression" => db_update
|
||||
.player_progression
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_code_operation" => db_update
|
||||
.profile_code_operation
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_dashboard_state" => db_update
|
||||
.profile_dashboard_state
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
@@ -4770,6 +4792,7 @@ pub struct AppliedDiff<'r> {
|
||||
match_3_d_work_profile: __sdk::TableAppliedDiff<'r, Match3DWorkProfileRow>,
|
||||
npc_state: __sdk::TableAppliedDiff<'r, NpcState>,
|
||||
player_progression: __sdk::TableAppliedDiff<'r, PlayerProgression>,
|
||||
profile_code_operation: __sdk::TableAppliedDiff<'r, ProfileCodeOperation>,
|
||||
profile_dashboard_state: __sdk::TableAppliedDiff<'r, ProfileDashboardState>,
|
||||
profile_feedback_submission: __sdk::TableAppliedDiff<'r, ProfileFeedbackSubmission>,
|
||||
profile_invite_code: __sdk::TableAppliedDiff<'r, ProfileInviteCode>,
|
||||
@@ -5142,6 +5165,11 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
|
||||
&self.player_progression,
|
||||
event,
|
||||
);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileCodeOperation>(
|
||||
"profile_code_operation",
|
||||
&self.profile_code_operation,
|
||||
event,
|
||||
);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileDashboardState>(
|
||||
"profile_dashboard_state",
|
||||
&self.profile_dashboard_state,
|
||||
@@ -6180,6 +6208,7 @@ impl __sdk::SpacetimeModule for RemoteModule {
|
||||
match_3_d_work_profile_table::register_table(client_cache);
|
||||
npc_state_table::register_table(client_cache);
|
||||
player_progression_table::register_table(client_cache);
|
||||
profile_code_operation_table::register_table(client_cache);
|
||||
profile_dashboard_state_table::register_table(client_cache);
|
||||
profile_feedback_submission_table::register_table(client_cache);
|
||||
profile_invite_code_table::register_table(client_cache);
|
||||
@@ -6308,6 +6337,7 @@ impl __sdk::SpacetimeModule for RemoteModule {
|
||||
"match_3_d_work_profile",
|
||||
"npc_state",
|
||||
"player_progression",
|
||||
"profile_code_operation",
|
||||
"profile_dashboard_state",
|
||||
"profile_feedback_submission",
|
||||
"profile_invite_code",
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use super::profile_code_operation_type::ProfileCodeOperation;
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
/// Table handle for the table `profile_code_operation`.
|
||||
///
|
||||
/// Obtain a handle from the [`ProfileCodeOperationTableAccess::profile_code_operation`] method on [`super::RemoteTables`],
|
||||
/// like `ctx.db.profile_code_operation()`.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_code_operation().on_insert(...)`.
|
||||
pub struct ProfileCodeOperationTableHandle<'ctx> {
|
||||
imp: __sdk::TableHandle<ProfileCodeOperation>,
|
||||
ctx: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the table `profile_code_operation`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteTables`].
|
||||
pub trait ProfileCodeOperationTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Obtain a [`ProfileCodeOperationTableHandle`], which mediates access to the table `profile_code_operation`.
|
||||
fn profile_code_operation(&self) -> ProfileCodeOperationTableHandle<'_>;
|
||||
}
|
||||
|
||||
impl ProfileCodeOperationTableAccess for super::RemoteTables {
|
||||
fn profile_code_operation(&self) -> ProfileCodeOperationTableHandle<'_> {
|
||||
ProfileCodeOperationTableHandle {
|
||||
imp: self
|
||||
.imp
|
||||
.get_table::<ProfileCodeOperation>("profile_code_operation"),
|
||||
ctx: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileCodeOperationInsertCallbackId(__sdk::CallbackId);
|
||||
pub struct ProfileCodeOperationDeleteCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::Table for ProfileCodeOperationTableHandle<'ctx> {
|
||||
type Row = ProfileCodeOperation;
|
||||
type EventContext = super::EventContext;
|
||||
|
||||
fn count(&self) -> u64 {
|
||||
self.imp.count()
|
||||
}
|
||||
fn iter(&self) -> impl Iterator<Item = ProfileCodeOperation> + '_ {
|
||||
self.imp.iter()
|
||||
}
|
||||
|
||||
type InsertCallbackId = ProfileCodeOperationInsertCallbackId;
|
||||
|
||||
fn on_insert(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileCodeOperationInsertCallbackId {
|
||||
ProfileCodeOperationInsertCallbackId(self.imp.on_insert(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_insert(&self, callback: ProfileCodeOperationInsertCallbackId) {
|
||||
self.imp.remove_on_insert(callback.0)
|
||||
}
|
||||
|
||||
type DeleteCallbackId = ProfileCodeOperationDeleteCallbackId;
|
||||
|
||||
fn on_delete(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static,
|
||||
) -> ProfileCodeOperationDeleteCallbackId {
|
||||
ProfileCodeOperationDeleteCallbackId(self.imp.on_delete(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_delete(&self, callback: ProfileCodeOperationDeleteCallbackId) {
|
||||
self.imp.remove_on_delete(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProfileCodeOperationUpdateCallbackId(__sdk::CallbackId);
|
||||
|
||||
impl<'ctx> __sdk::TableWithPrimaryKey for ProfileCodeOperationTableHandle<'ctx> {
|
||||
type UpdateCallbackId = ProfileCodeOperationUpdateCallbackId;
|
||||
|
||||
fn on_update(
|
||||
&self,
|
||||
callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static,
|
||||
) -> ProfileCodeOperationUpdateCallbackId {
|
||||
ProfileCodeOperationUpdateCallbackId(self.imp.on_update(Box::new(callback)))
|
||||
}
|
||||
|
||||
fn remove_on_update(&self, callback: ProfileCodeOperationUpdateCallbackId) {
|
||||
self.imp.remove_on_update(callback.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the `operation_id` unique index on the table `profile_code_operation`,
|
||||
/// which allows point queries on the field of the same name
|
||||
/// via the [`ProfileCodeOperationOperationIdUnique::find`] method.
|
||||
///
|
||||
/// Users are encouraged not to explicitly reference this type,
|
||||
/// but to directly chain method calls,
|
||||
/// like `ctx.db.profile_code_operation().operation_id().find(...)`.
|
||||
pub struct ProfileCodeOperationOperationIdUnique<'ctx> {
|
||||
imp: __sdk::UniqueConstraintHandle<ProfileCodeOperation, String>,
|
||||
phantom: std::marker::PhantomData<&'ctx super::RemoteTables>,
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileCodeOperationTableHandle<'ctx> {
|
||||
/// Get a handle on the `operation_id` unique index on the table `profile_code_operation`.
|
||||
pub fn operation_id(&self) -> ProfileCodeOperationOperationIdUnique<'ctx> {
|
||||
ProfileCodeOperationOperationIdUnique {
|
||||
imp: self.imp.get_unique_constraint::<String>("operation_id"),
|
||||
phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProfileCodeOperationOperationIdUnique<'ctx> {
|
||||
/// Find the subscribed row whose `operation_id` column value is equal to `col_val`,
|
||||
/// if such a row is present in the client cache.
|
||||
pub fn find(&self, col_val: &String) -> Option<ProfileCodeOperation> {
|
||||
self.imp.find(col_val)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn register_table(client_cache: &mut __sdk::ClientCache<super::RemoteModule>) {
|
||||
let _table = client_cache.get_or_make_table::<ProfileCodeOperation>("profile_code_operation");
|
||||
_table.add_unique_constraint::<String>("operation_id", |row| &row.operation_id);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(super) fn parse_table_update(
|
||||
raw_updates: __ws::v2::TableUpdate,
|
||||
) -> __sdk::Result<__sdk::TableUpdate<ProfileCodeOperation>> {
|
||||
__sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| {
|
||||
__sdk::InternalError::failed_parse("TableUpdate<ProfileCodeOperation>", "TableUpdate")
|
||||
.with_cause(e)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for query builder access to the table `ProfileCodeOperation`.
|
||||
///
|
||||
/// Implemented for [`__sdk::QueryTableAccessor`].
|
||||
pub trait profile_code_operationQueryTableAccess {
|
||||
#[allow(non_snake_case)]
|
||||
/// Get a query builder for the table `ProfileCodeOperation`.
|
||||
fn profile_code_operation(&self) -> __sdk::__query_builder::Table<ProfileCodeOperation>;
|
||||
}
|
||||
|
||||
impl profile_code_operationQueryTableAccess for __sdk::QueryTableAccessor {
|
||||
fn profile_code_operation(&self) -> __sdk::__query_builder::Table<ProfileCodeOperation> {
|
||||
__sdk::__query_builder::Table::new("profile_code_operation")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct ProfileCodeOperation {
|
||||
pub operation_id: String,
|
||||
pub code_kind: String,
|
||||
pub code: String,
|
||||
pub action: String,
|
||||
pub operator_user_id: String,
|
||||
pub created_at: __sdk::Timestamp,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for ProfileCodeOperation {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
/// Column accessor struct for the table `ProfileCodeOperation`.
|
||||
///
|
||||
/// Provides typed access to columns for query building.
|
||||
pub struct ProfileCodeOperationCols {
|
||||
pub operation_id: __sdk::__query_builder::Col<ProfileCodeOperation, String>,
|
||||
pub code_kind: __sdk::__query_builder::Col<ProfileCodeOperation, String>,
|
||||
pub code: __sdk::__query_builder::Col<ProfileCodeOperation, String>,
|
||||
pub action: __sdk::__query_builder::Col<ProfileCodeOperation, String>,
|
||||
pub operator_user_id: __sdk::__query_builder::Col<ProfileCodeOperation, String>,
|
||||
pub created_at: __sdk::__query_builder::Col<ProfileCodeOperation, __sdk::Timestamp>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasCols for ProfileCodeOperation {
|
||||
type Cols = ProfileCodeOperationCols;
|
||||
fn cols(table_name: &'static str) -> Self::Cols {
|
||||
ProfileCodeOperationCols {
|
||||
operation_id: __sdk::__query_builder::Col::new(table_name, "operation_id"),
|
||||
code_kind: __sdk::__query_builder::Col::new(table_name, "code_kind"),
|
||||
code: __sdk::__query_builder::Col::new(table_name, "code"),
|
||||
action: __sdk::__query_builder::Col::new(table_name, "action"),
|
||||
operator_user_id: __sdk::__query_builder::Col::new(table_name, "operator_user_id"),
|
||||
created_at: __sdk::__query_builder::Col::new(table_name, "created_at"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indexed column accessor struct for the table `ProfileCodeOperation`.
|
||||
///
|
||||
/// Provides typed access to indexed columns for query building.
|
||||
pub struct ProfileCodeOperationIxCols {
|
||||
pub code_kind: __sdk::__query_builder::IxCol<ProfileCodeOperation, String>,
|
||||
pub operation_id: __sdk::__query_builder::IxCol<ProfileCodeOperation, String>,
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::HasIxCols for ProfileCodeOperation {
|
||||
type IxCols = ProfileCodeOperationIxCols;
|
||||
fn ix_cols(table_name: &'static str) -> Self::IxCols {
|
||||
ProfileCodeOperationIxCols {
|
||||
code_kind: __sdk::__query_builder::IxCol::new(table_name, "code_kind"),
|
||||
operation_id: __sdk::__query_builder::IxCol::new(table_name, "operation_id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl __sdk::__query_builder::CanBeLookupTable for ProfileCodeOperation {}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct RuntimeProfileCodeOperationSnapshot {
|
||||
pub operation_id: String,
|
||||
pub code_kind: String,
|
||||
pub code: String,
|
||||
pub action: String,
|
||||
pub operator_user_id: String,
|
||||
pub created_at_micros: i64,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for RuntimeProfileCodeOperationSnapshot {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+2
@@ -4,6 +4,7 @@
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::runtime_profile_code_operation_snapshot_type::RuntimeProfileCodeOperationSnapshot;
|
||||
use super::runtime_profile_invite_code_snapshot_type::RuntimeProfileInviteCodeSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
@@ -11,6 +12,7 @@ use super::runtime_profile_invite_code_snapshot_type::RuntimeProfileInviteCodeSn
|
||||
pub struct RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileInviteCodeSnapshot>,
|
||||
pub operations: Vec<RuntimeProfileCodeOperationSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -4,6 +4,7 @@
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::runtime_profile_code_operation_snapshot_type::RuntimeProfileCodeOperationSnapshot;
|
||||
use super::runtime_profile_redeem_code_snapshot_type::RuntimeProfileRedeemCodeSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
@@ -11,6 +12,7 @@ use super::runtime_profile_redeem_code_snapshot_type::RuntimeProfileRedeemCodeSn
|
||||
pub struct RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileRedeemCodeSnapshot>,
|
||||
pub operations: Vec<RuntimeProfileCodeOperationSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1138,7 +1138,7 @@ impl SpacetimeClient {
|
||||
pub async fn admin_list_profile_redeem_codes(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
) -> Result<Vec<RuntimeProfileRedeemCodeRecord>, SpacetimeClientError> {
|
||||
) -> Result<RuntimeProfileRedeemCodeAdminListRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_redeem_code_admin_list_input(admin_user_id)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
@@ -1228,7 +1228,7 @@ impl SpacetimeClient {
|
||||
pub async fn admin_list_profile_invite_codes(
|
||||
&self,
|
||||
admin_user_id: String,
|
||||
) -> Result<Vec<RuntimeProfileInviteCodeRecord>, SpacetimeClientError> {
|
||||
) -> Result<RuntimeProfileInviteCodeAdminListRecord, SpacetimeClientError> {
|
||||
let procedure_input = build_runtime_profile_invite_code_admin_list_input(admin_user_id)
|
||||
.map_err(SpacetimeClientError::validation_failed)?
|
||||
.into();
|
||||
|
||||
@@ -200,6 +200,7 @@ macro_rules! migration_tables {
|
||||
profile_task_reward_claim,
|
||||
profile_redeem_code,
|
||||
profile_redeem_code_usage,
|
||||
profile_code_operation,
|
||||
profile_invite_code,
|
||||
profile_referral_relation,
|
||||
profile_played_world,
|
||||
|
||||
@@ -194,6 +194,27 @@ pub struct ProfileRedeemCodeUsage {
|
||||
pub(crate) created_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(
|
||||
accessor = profile_code_operation,
|
||||
index(
|
||||
accessor = by_profile_code_operation_code_kind,
|
||||
btree(columns = [code_kind])
|
||||
),
|
||||
index(
|
||||
accessor = by_profile_code_operation_kind_code,
|
||||
btree(columns = [code_kind, code])
|
||||
)
|
||||
)]
|
||||
pub struct ProfileCodeOperation {
|
||||
#[primary_key]
|
||||
pub(crate) operation_id: String,
|
||||
pub(crate) code_kind: String,
|
||||
pub(crate) code: String,
|
||||
pub(crate) action: String,
|
||||
pub(crate) operator_user_id: String,
|
||||
pub(crate) created_at: Timestamp,
|
||||
}
|
||||
|
||||
#[spacetimedb::table(accessor = profile_invite_code)]
|
||||
pub struct ProfileInviteCode {
|
||||
#[primary_key]
|
||||
@@ -1097,14 +1118,16 @@ pub fn admin_list_profile_redeem_codes(
|
||||
input: RuntimeProfileRedeemCodeAdminListInput,
|
||||
) -> RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
match ctx.try_with_tx(|tx| admin_list_profile_redeem_code_records(tx, input.clone())) {
|
||||
Ok(entries) => RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
Ok((entries, operations)) => RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
ok: true,
|
||||
entries,
|
||||
operations,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileRedeemCodeAdminListProcedureResult {
|
||||
ok: false,
|
||||
entries: Vec::new(),
|
||||
operations: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
@@ -1135,14 +1158,16 @@ pub fn admin_list_profile_invite_codes(
|
||||
input: RuntimeProfileInviteCodeAdminListInput,
|
||||
) -> RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
match ctx.try_with_tx(|tx| admin_list_profile_invite_code_records(tx, input.clone())) {
|
||||
Ok(entries) => RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
Ok((entries, operations)) => RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
ok: true,
|
||||
entries,
|
||||
operations,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => RuntimeProfileInviteCodeAdminListProcedureResult {
|
||||
ok: false,
|
||||
entries: Vec::new(),
|
||||
operations: Vec::new(),
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
@@ -2537,6 +2562,13 @@ fn admin_upsert_profile_redeem_code_record(
|
||||
.profile_redeem_code()
|
||||
.code()
|
||||
.find(&validated_input.code);
|
||||
let action = if existing.is_some() {
|
||||
"update"
|
||||
} else {
|
||||
"create"
|
||||
};
|
||||
let operation_code = validated_input.code.clone();
|
||||
let operator_user_id = validated_input.admin_user_id.clone();
|
||||
let created_at = existing
|
||||
.as_ref()
|
||||
.map(|row| row.created_at)
|
||||
@@ -2563,6 +2595,14 @@ fn admin_upsert_profile_redeem_code_record(
|
||||
updated_at,
|
||||
};
|
||||
let inserted = ctx.db.profile_redeem_code().insert(row);
|
||||
insert_profile_code_operation(
|
||||
ctx,
|
||||
"redeem",
|
||||
&operation_code,
|
||||
action,
|
||||
&operator_user_id,
|
||||
updated_at,
|
||||
);
|
||||
Ok(build_profile_redeem_code_snapshot_from_row(&inserted))
|
||||
}
|
||||
|
||||
@@ -2590,6 +2630,14 @@ fn admin_disable_profile_redeem_code_record(
|
||||
updated_at,
|
||||
..existing
|
||||
});
|
||||
insert_profile_code_operation(
|
||||
ctx,
|
||||
"redeem",
|
||||
&validated_input.code,
|
||||
"disable",
|
||||
&validated_input.admin_user_id,
|
||||
updated_at,
|
||||
);
|
||||
Ok(build_profile_redeem_code_snapshot_from_row(&inserted))
|
||||
}
|
||||
|
||||
@@ -2611,6 +2659,8 @@ fn admin_upsert_profile_invite_code_record(
|
||||
&validated_input.admin_user_id,
|
||||
&validated_input.invite_code,
|
||||
);
|
||||
let operation_code = validated_input.invite_code.clone();
|
||||
let operator_user_id = validated_input.admin_user_id.clone();
|
||||
|
||||
if let Some(existing) = ctx
|
||||
.db
|
||||
@@ -2638,6 +2688,14 @@ fn admin_upsert_profile_invite_code_record(
|
||||
.expires_at_micros
|
||||
.map(Timestamp::from_micros_since_unix_epoch),
|
||||
});
|
||||
insert_profile_code_operation(
|
||||
ctx,
|
||||
"invite",
|
||||
&operation_code,
|
||||
"update",
|
||||
&operator_user_id,
|
||||
updated_at,
|
||||
);
|
||||
return Ok(build_profile_invite_code_snapshot_from_row(&inserted));
|
||||
}
|
||||
|
||||
@@ -2654,6 +2712,14 @@ fn admin_upsert_profile_invite_code_record(
|
||||
.expires_at_micros
|
||||
.map(Timestamp::from_micros_since_unix_epoch),
|
||||
});
|
||||
insert_profile_code_operation(
|
||||
ctx,
|
||||
"invite",
|
||||
&operation_code,
|
||||
"create",
|
||||
&operator_user_id,
|
||||
updated_at,
|
||||
);
|
||||
Ok(build_profile_invite_code_snapshot_from_row(&inserted))
|
||||
}
|
||||
|
||||
@@ -3171,7 +3237,13 @@ fn list_profile_recharge_product_config_snapshots(
|
||||
fn admin_list_profile_redeem_code_records(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileRedeemCodeAdminListInput,
|
||||
) -> Result<Vec<RuntimeProfileRedeemCodeSnapshot>, String> {
|
||||
) -> Result<
|
||||
(
|
||||
Vec<RuntimeProfileRedeemCodeSnapshot>,
|
||||
Vec<RuntimeProfileCodeOperationSnapshot>,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
let _validated_input = build_runtime_profile_redeem_code_admin_list_input(input.admin_user_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
@@ -3187,13 +3259,19 @@ fn admin_list_profile_redeem_code_records(
|
||||
.cmp(&left.updated_at_micros)
|
||||
.then_with(|| left.code.cmp(&right.code))
|
||||
});
|
||||
Ok(entries)
|
||||
Ok((entries, profile_code_operation_snapshots(ctx, "redeem")))
|
||||
}
|
||||
|
||||
fn admin_list_profile_invite_code_records(
|
||||
ctx: &ReducerContext,
|
||||
input: RuntimeProfileInviteCodeAdminListInput,
|
||||
) -> Result<Vec<RuntimeProfileInviteCodeSnapshot>, String> {
|
||||
) -> Result<
|
||||
(
|
||||
Vec<RuntimeProfileInviteCodeSnapshot>,
|
||||
Vec<RuntimeProfileCodeOperationSnapshot>,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
let _validated_input = build_runtime_profile_invite_code_admin_list_input(input.admin_user_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
@@ -3210,7 +3288,7 @@ fn admin_list_profile_invite_code_records(
|
||||
.cmp(&left.updated_at_micros)
|
||||
.then_with(|| left.invite_code.cmp(&right.invite_code))
|
||||
});
|
||||
Ok(entries)
|
||||
Ok((entries, profile_code_operation_snapshots(ctx, "invite")))
|
||||
}
|
||||
|
||||
fn upsert_profile_recharge_product_config_record(
|
||||
@@ -3531,6 +3609,59 @@ fn profile_task_tracking_scope_id(
|
||||
Ok(user_id.to_string())
|
||||
}
|
||||
|
||||
fn insert_profile_code_operation(
|
||||
ctx: &ReducerContext,
|
||||
code_kind: &str,
|
||||
code: &str,
|
||||
action: &str,
|
||||
operator_user_id: &str,
|
||||
created_at: Timestamp,
|
||||
) {
|
||||
let created_at_micros = created_at.to_micros_since_unix_epoch();
|
||||
let operation_index = ctx
|
||||
.db
|
||||
.profile_code_operation()
|
||||
.by_profile_code_operation_kind_code()
|
||||
.filter((code_kind, code))
|
||||
.count();
|
||||
ctx.db.profile_code_operation().insert(ProfileCodeOperation {
|
||||
operation_id: format!(
|
||||
"{}:{}:{}:{}:{}",
|
||||
code_kind.trim(),
|
||||
code.trim(),
|
||||
action.trim(),
|
||||
created_at_micros,
|
||||
operation_index
|
||||
),
|
||||
code_kind: code_kind.to_string(),
|
||||
code: code.to_string(),
|
||||
action: action.to_string(),
|
||||
operator_user_id: operator_user_id.to_string(),
|
||||
created_at,
|
||||
});
|
||||
}
|
||||
|
||||
fn profile_code_operation_snapshots(
|
||||
ctx: &ReducerContext,
|
||||
code_kind: &str,
|
||||
) -> Vec<RuntimeProfileCodeOperationSnapshot> {
|
||||
let mut entries = ctx
|
||||
.db
|
||||
.profile_code_operation()
|
||||
.by_profile_code_operation_code_kind()
|
||||
.filter(code_kind)
|
||||
.map(|row| build_profile_code_operation_snapshot_from_row(&row))
|
||||
.collect::<Vec<_>>();
|
||||
entries.sort_by(|left, right| {
|
||||
right
|
||||
.created_at_micros
|
||||
.cmp(&left.created_at_micros)
|
||||
.then_with(|| left.code.cmp(&right.code))
|
||||
.then_with(|| left.operation_id.cmp(&right.operation_id))
|
||||
});
|
||||
entries
|
||||
}
|
||||
|
||||
fn validate_profile_task_user_scope(config: &ProfileTaskConfig) -> Result<(), String> {
|
||||
if config.scope_kind == RuntimeTrackingScopeKind::User {
|
||||
Ok(())
|
||||
@@ -4165,6 +4296,19 @@ fn build_profile_invite_code_snapshot_from_row(
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_code_operation_snapshot_from_row(
|
||||
row: &ProfileCodeOperation,
|
||||
) -> RuntimeProfileCodeOperationSnapshot {
|
||||
RuntimeProfileCodeOperationSnapshot {
|
||||
operation_id: row.operation_id.clone(),
|
||||
code_kind: row.code_kind.clone(),
|
||||
code: row.code.clone(),
|
||||
action: row.action.clone(),
|
||||
operator_user_id: row.operator_user_id.clone(),
|
||||
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_profile_wallet_ledger_snapshot_from_row(
|
||||
row: &ProfileWalletLedger,
|
||||
) -> RuntimeProfileWalletLedgerEntrySnapshot {
|
||||
|
||||
Reference in New Issue
Block a user