stable
This commit is contained in:
parent
71009c551f
commit
80aae72feb
10 changed files with 830 additions and 5041 deletions
|
|
@ -8,7 +8,10 @@ function fb_respond(array $payload, int $status = 200): void {
|
|||
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
function fb_fail(string $message, int $status = 400, array $extra = []): void { fb_respond(['ok' => false, 'error' => $message] + $extra, $status); }
|
||||
function fb_fail(string $message, int $status = 400, array $extra = []): void {
|
||||
if (!empty($GLOBALS['PIXEL_ISLAND_COMMAND_CONTEXT'])) throw new RuntimeException($message, $status);
|
||||
fb_respond(['ok' => false, 'error' => $message] + $extra, $status);
|
||||
}
|
||||
function fb_now(): int { return (int) floor(microtime(true) * 1000); }
|
||||
function fb_clean_text($value, string $fallback = '', int $max = 80): string {
|
||||
$text = trim((string)($value ?? ''));
|
||||
|
|
@ -33,6 +36,37 @@ function fb_request_json(int $maxBytes): array {
|
|||
function fb_default_db(): array {
|
||||
return ['schema'=>1,'accounts'=>[],'assets'=>[],'objects'=>[],'votes'=>[],'reports'=>[],'events'=>[],'commands'=>[],'nextEventId'=>1];
|
||||
}
|
||||
|
||||
function fb_seed_path(array $config): string {
|
||||
return dirname($config['db_path']) . DIRECTORY_SEPARATOR . 'default_gallery_seed.json';
|
||||
}
|
||||
function fb_seed_defaults(array &$db, array $config): void {
|
||||
$path = fb_seed_path($config);
|
||||
if (!is_file($path)) return;
|
||||
$seed = json_decode((string)file_get_contents($path), true);
|
||||
if (!is_array($seed)) return;
|
||||
$now = fb_now();
|
||||
if (!isset($db['accounts']['island-team'])) $db['accounts']['island-team'] = ['id'=>'island-team','name'=>'Pixel Island','password_hash'=>password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT),'created_at'=>$now,'updated_at'=>$now,'disabled_at'=>null];
|
||||
foreach (is_array($seed['assets'] ?? null) ? $seed['assets'] : [] as $asset) {
|
||||
if (!is_array($asset)) continue;
|
||||
$id = fb_clean_id($asset['id'] ?? '');
|
||||
if ($id === '' || isset($db['assets'][$id])) continue;
|
||||
$asset['ownerAccountId'] = 'island-team';
|
||||
$asset['author'] = $asset['author'] ?? 'Pixel Island';
|
||||
$db['assets'][$id]=['id'=>$id,'owner_account_id'=>'island-team','author_name'=>fb_clean_text($asset['author'] ?? 'Pixel Island','Pixel Island',32),'json'=>$asset,'version'=>max(1,(int)($asset['version'] ?? 1)),'created_at'=>(int)($asset['createdAt'] ?? $now),'updated_at'=>(int)($asset['updatedAt'] ?? $now),'deleted_at'=>null,'deleted_by'=>null,'content_hash'=>$asset['contentHash'] ?? null];
|
||||
}
|
||||
$insertObject = function(array $object, string $kind) use (&$db, $now): void {
|
||||
$id = fb_clean_id($object['id'] ?? '');
|
||||
$assetId = fb_clean_id($object['assetId'] ?? '');
|
||||
if ($id === '' || $assetId === '' || isset($db['objects'][$id]) || !isset($db['assets'][$assetId]) || !empty($db['assets'][$assetId]['deleted_at'])) return;
|
||||
$object['ownerAccountId']='island-team';
|
||||
$object['status']='active';
|
||||
$object['publishedAt']=(int)($object['publishedAt'] ?? $now);
|
||||
$db['objects'][$id]=['id'=>$id,'kind'=>$kind,'asset_id'=>$assetId,'owner_account_id'=>'island-team','json'=>$object,'version'=>max(1,(int)($object['version'] ?? 1)),'published_at'=>(int)$object['publishedAt'],'updated_at'=>$now,'deleted_at'=>null,'deleted_by'=>null,'moderation_status'=>'active'];
|
||||
};
|
||||
foreach (is_array($seed['placed'] ?? null) ? $seed['placed'] : [] as $object) if (is_array($object)) $insertObject($object, 'static');
|
||||
foreach (is_array($seed['dynamicSummons'] ?? null) ? $seed['dynamicSummons'] : [] as $object) if (is_array($object)) $insertObject($object, 'dynamic');
|
||||
}
|
||||
function fb_db_path(array $config): string {
|
||||
return dirname($config['db_path']) . DIRECTORY_SEPARATOR . 'pixel_island_filedb.json';
|
||||
}
|
||||
|
|
@ -121,10 +155,17 @@ function fb_quota(array $db, array $actor, array $config): bool {
|
|||
foreach ($db['objects'] as $o) if (($o['owner_account_id'] ?? '')===$actor['id'] && empty($o['deleted_at']) && (int)($o['published_at'] ?? 0) >= $since) $used++;
|
||||
return $used < $limit;
|
||||
}
|
||||
function fb_command_result(array &$db, string $cmdId, string $actorId, int $createdAt, array $result): void {
|
||||
$db['commands'][$cmdId]=['id'=>$cmdId,'actor_account_id'=>$actorId,'created_at'=>$createdAt,'applied_at'=>fb_now(),'result'=>$result];
|
||||
}
|
||||
function fb_process(array &$db, array $command, array $actor, array $config): array {
|
||||
$cmdId=fb_clean_id($command['id'] ?? ''); $type=(string)($command['type'] ?? '');
|
||||
if ($cmdId==='' || $type==='') return ['ok'=>false,'id'=>$cmdId,'error'=>'invalid_command'];
|
||||
if (isset($db['commands'][$cmdId])) return ['ok'=>true,'id'=>$cmdId,'duplicate'=>true];
|
||||
if (isset($db['commands'][$cmdId])) {
|
||||
$result = is_array($db['commands'][$cmdId]['result'] ?? null) ? $db['commands'][$cmdId]['result'] : ['ok'=>true,'id'=>$cmdId];
|
||||
$result['duplicate'] = true;
|
||||
return $result;
|
||||
}
|
||||
$now=fb_now();
|
||||
if ($type==='asset.create') {
|
||||
$asset=fb_normalize_asset(is_array($command['asset'] ?? null)?$command['asset']:[], $actor);
|
||||
|
|
@ -136,11 +177,30 @@ function fb_process(array &$db, array $command, array $actor, array $config): ar
|
|||
$db['assets'][$assetId]['deleted_at']=$now; $db['assets'][$assetId]['deleted_by']=$actor['id']; $db['assets'][$assetId]['updated_at']=$now;
|
||||
foreach ($db['objects'] as &$o) if (($o['asset_id'] ?? '')===$assetId && empty($o['deleted_at'])) { $o['deleted_at']=$now; $o['deleted_by']=$actor['id']; $o['updated_at']=$now; } unset($o);
|
||||
fb_event($db,'asset.delete',['type'=>'asset.delete','assetId'=>$assetId,'actorAccountId'=>$actor['id']]);
|
||||
} elseif ($type==='publish.asset_object') {
|
||||
$asset=fb_normalize_asset(is_array($command['asset'] ?? null)?$command['asset']:[], $actor);
|
||||
$kind=(($command['kind'] ?? $asset['category'] ?? '')==='dynamic')?'dynamic':'static';
|
||||
$object=fb_normalize_object(is_array($command['object'] ?? null)?$command['object']:[], $kind, $actor, $config);
|
||||
if (($object['assetId'] ?? '') !== ($asset['id'] ?? '')) fb_fail('publish_asset_mismatch',400);
|
||||
if (isset($db['assets'][$asset['id']]) && empty($db['assets'][$asset['id']]['deleted_at']) && ($db['assets'][$asset['id']]['owner_account_id'] ?? '') !== $actor['id']) fb_fail('asset_id_already_owned',403);
|
||||
if (!fb_quota($db,$actor,$config)) fb_fail('publish_limit_reached',429);
|
||||
$active=0; foreach($db['objects'] as $o) if (empty($o['deleted_at']) && ($o['moderation_status'] ?? 'active')==='active') $active++;
|
||||
if ($active >= (int)$config['max_world_objects']) fb_fail('world_object_limit_reached',409);
|
||||
$db['assets'][$asset['id']]=['id'=>$asset['id'],'owner_account_id'=>$actor['id'],'author_name'=>$asset['author'],'json'=>$asset,'version'=>$asset['version'],'created_at'=>$asset['createdAt'],'updated_at'=>$asset['updatedAt'],'deleted_at'=>null,'deleted_by'=>null,'content_hash'=>$asset['contentHash'] ?? null];
|
||||
$object['publishedAt']=$now;
|
||||
if ($kind==='dynamic') $object['createdAt']=$now; else $object['placedAt']=$now;
|
||||
$db['objects'][$object['id']]=['id'=>$object['id'],'kind'=>$kind,'asset_id'=>$object['assetId'],'owner_account_id'=>$actor['id'],'json'=>$object,'version'=>$object['version'],'published_at'=>$now,'updated_at'=>$now,'deleted_at'=>null,'deleted_by'=>null,'moderation_status'=>'active'];
|
||||
fb_event($db,'asset.upsert',['type'=>'asset.upsert','assetId'=>$asset['id'],'actorAccountId'=>$actor['id']]);
|
||||
fb_event($db,'object.upsert',['type'=>'object.upsert','kind'=>$kind,'objectId'=>$object['id'],'actorAccountId'=>$actor['id']]);
|
||||
$result=['ok'=>true,'id'=>$cmdId,'type'=>$type,'assetId'=>$asset['id'],'objectId'=>$object['id'],'kind'=>$kind,'publishedAt'=>$now,'object'=>$object];
|
||||
fb_command_result($db,$cmdId,$actor['id'],(int)($command['createdAt'] ?? $now),$result);
|
||||
return $result;
|
||||
} elseif ($type==='object.publish' || $type==='object.move') {
|
||||
$kind=(($command['kind'] ?? '')==='dynamic')?'dynamic':'static'; $object=fb_normalize_object(is_array($command['object'] ?? null)?$command['object']:[], $kind, $actor, $config); fb_asset_owner($db,$object['assetId'],$actor['id']);
|
||||
if ($type==='object.publish' && !fb_quota($db,$actor,$config)) fb_fail('publish_limit_reached',429);
|
||||
$active=0; foreach($db['objects'] as $o) if (empty($o['deleted_at']) && ($o['moderation_status'] ?? 'active')==='active') $active++;
|
||||
if ($type==='object.publish' && $active >= (int)$config['max_world_objects']) fb_fail('world_object_limit_reached',409);
|
||||
if ($type==='object.publish') $object['publishedAt']=$now;
|
||||
$db['objects'][$object['id']]=['id'=>$object['id'],'kind'=>$kind,'asset_id'=>$object['assetId'],'owner_account_id'=>$actor['id'],'json'=>$object,'version'=>$object['version'],'published_at'=>(int)$object['publishedAt'],'updated_at'=>$now,'deleted_at'=>null,'deleted_by'=>null,'moderation_status'=>'active'];
|
||||
fb_event($db,'object.upsert',['type'=>'object.upsert','kind'=>$kind,'objectId'=>$object['id'],'actorAccountId'=>$actor['id']]);
|
||||
} elseif ($type==='object.delete') {
|
||||
|
|
@ -151,8 +211,9 @@ function fb_process(array &$db, array $command, array $actor, array $config): ar
|
|||
} elseif ($type==='report.object') {
|
||||
$objectId=fb_clean_id($command['objectId'] ?? ''); $assetId=fb_clean_id($command['assetId'] ?? ''); $kind=(($command['objectKind'] ?? $command['kind'] ?? '')==='dynamic')?'dynamic':'static'; $reason=fb_clean_text($command['reason'] ?? 'other','other',48); if($objectId==='' || $assetId==='') fb_fail('report_target_required'); $reportId=fb_clean_id($command['reportId'] ?? $command['id'] ?? ('report_'.bin2hex(random_bytes(6)))); $db['reports'][$reportId]=['id'=>$reportId,'object_id'=>$objectId,'asset_id'=>$assetId,'object_kind'=>$kind,'reporter_account_id'=>$actor['id'],'reason'=>$reason,'created_at'=>$now,'status'=>'open']; fb_event($db,'report.object',['type'=>'report.object','objectId'=>$objectId,'assetId'=>$assetId,'actorAccountId'=>$actor['id'],'reason'=>$reason]);
|
||||
} else return ['ok'=>false,'id'=>$cmdId,'error'=>'unsupported_command'];
|
||||
$db['commands'][$cmdId]=['id'=>$cmdId,'actor_account_id'=>$actor['id'],'created_at'=>(int)($command['createdAt'] ?? $now),'applied_at'=>$now];
|
||||
return ['ok'=>true,'id'=>$cmdId];
|
||||
$result=['ok'=>true,'id'=>$cmdId,'type'=>$type];
|
||||
fb_command_result($db,$cmdId,$actor['id'],(int)($command['createdAt'] ?? $now),$result);
|
||||
return $result;
|
||||
}
|
||||
function fb_votes(array $db, string $targetType): array { $out=[]; foreach($db['votes'] as $v){ if(($v['target_type'] ?? '')!==$targetType || (int)($v['value'] ?? 0)===0) continue; $id=$v['target_id']; $val=(int)$v['value']; if(!isset($out[$id])) $out[$id]=['up'=>0,'down'=>0,'voters'=>[]]; if($val>0)$out[$id]['up']++; if($val<0)$out[$id]['down']++; $out[$id]['voters'][$v['voter_account_id']]=$val; } return $out; }
|
||||
function fb_snapshot(array $db): array {
|
||||
|
|
@ -169,8 +230,9 @@ function fb_snapshot(array $db): array {
|
|||
$action = $_GET['action'] ?? '';
|
||||
if ($action === 'health' || $action === '') fb_respond(['ok'=>true,'service'=>'pixel-island-api','serverNow'=>fb_now(),'sqlite'=>false,'fileDb'=>true,'note'=>'pdo_sqlite is unavailable; using locked JSON fallback']);
|
||||
fb_with_db($config, function(array &$db) use ($action, $config) {
|
||||
fb_seed_defaults($db, $config);
|
||||
if ($action === 'snapshot') return fb_snapshot($db);
|
||||
if ($action === 'account') { $data=fb_request_json((int)$config['max_json_bytes']); $account=fb_auth($db, is_array($data['account'] ?? null)?$data['account']:$data, true); return ['ok'=>true,'account'=>['id'=>$account['id'],'name'=>$account['name'],'createdAt'=>(int)$account['created_at']]]; }
|
||||
if ($action === 'commands') { $data=fb_request_json((int)$config['max_json_bytes']); $actor=fb_auth($db, is_array($data['account'] ?? null)?$data['account']:[], true); $commands=is_array($data['commands'] ?? null)?$data['commands']:[]; if(count($commands)>100) fb_fail('too_many_commands',413); $applied=[]; $rejected=[]; foreach($commands as $command){ if(!is_array($command)) continue; try{ $result=fb_process($db,$command,$actor,$config); if($result['ok'] ?? false) $applied[]=$result['id']; else $rejected[]=$result; } catch(Throwable $e){ $rejected[]=['ok'=>false,'id'=>fb_clean_id($command['id'] ?? ''),'error'=>$e->getMessage()]; } } return ['ok'=>true,'appliedCommandIds'=>$applied,'rejectedCommands'=>$rejected,'snapshot'=>fb_snapshot($db)]; }
|
||||
if ($action === 'commands') { $data=fb_request_json((int)$config['max_json_bytes']); $actor=fb_auth($db, is_array($data['account'] ?? null)?$data['account']:[], true); $commands=is_array($data['commands'] ?? null)?$data['commands']:[]; if(count($commands)>100) fb_fail('too_many_commands',413); $applied=[]; $rejected=[]; foreach($commands as $command){ if(!is_array($command)) continue; try{ $GLOBALS['PIXEL_ISLAND_COMMAND_CONTEXT']=true; $result=fb_process($db,$command,$actor,$config); $GLOBALS['PIXEL_ISLAND_COMMAND_CONTEXT']=false; if($result['ok'] ?? false) $applied[]=$result['id']; else $rejected[]=$result; } catch(Throwable $e){ $GLOBALS['PIXEL_ISLAND_COMMAND_CONTEXT']=false; $id=fb_clean_id($command['id'] ?? ''); $result=['ok'=>false,'id'=>$id,'type'=>(string)($command['type'] ?? ''),'error'=>$e->getMessage()]; if($id!=='') fb_command_result($db,$id,$actor['id'],(int)($command['createdAt'] ?? fb_now()),$result); $rejected[]=$result; } } return ['ok'=>true,'appliedCommandIds'=>$applied,'rejectedCommands'=>$rejected,'snapshot'=>fb_snapshot($db)]; }
|
||||
return ['ok'=>false,'error'=>'unknown_action'];
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue