seed.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * Railway: demo content for a fresh install, so production and previews are not empty.
  7. * Runs from entrypoint.sh when UNA_DEMO_SEED=1. Idempotent: content is created once, and
  8. * skipped once the first demo account exists; seedPeople() runs on every start and does only
  9. * what is missing. Content goes through module services (not raw SQL), so timeline events,
  10. * counters and notifications are created the same way as by users.
  11. *
  12. * Environment:
  13. * UNA_DEMO_PASSWORD password for the demo accounts (random if unset)
  14. */
  15. ob_start(); // bx_login() sets cookies; nothing to send them to in CLI
  16. $_SERVER['HTTP_HOST'] = getenv('RAILWAY_PUBLIC_DOMAIN') ?: 'localhost';
  17. $_SERVER['REQUEST_URI'] = '/';
  18. $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST'];
  19. $GLOBALS['bx_profiler_disable'] = true;
  20. require_once('/var/www/html/inc/header.inc.php');
  21. function seedLog(string $s): void
  22. {
  23. fwrite(STDERR, "[seed] $s\n");
  24. }
  25. $aUsers = [
  26. ['name' => 'Lily Carter', 'email' => 'lily@example.com'],
  27. ['name' => 'Eric Novak', 'email' => 'eric@example.com'],
  28. ['name' => 'Mila Santos', 'email' => 'mila@example.com'],
  29. ['name' => 'Tom Walsh', 'email' => 'tom@example.com'],
  30. ['name' => 'Ella Kim', 'email' => 'ella@example.com'],
  31. ];
  32. /**
  33. * Confirm the demo accounts and connect them, so friend and follow suggestions have people to
  34. * offer. Runs on every start, so sites seeded before this existed catch up.
  35. * - createAccount() takes join form fields only, so it can't confirm an email, and
  36. * sys_account_hide_unconfirmed_accounts hides unconfirmed accounts from suggestions.
  37. * - Friend suggestions come from mutual friends, follow suggestions from the accounts that the
  38. * people you follow follow. A chain of friends and a ring of follows give everyone both.
  39. */
  40. function seedPeople(array $aUsers): void
  41. {
  42. $aPersons = [];
  43. $iConfirmed = 0;
  44. foreach ($aUsers as $aUser) {
  45. if (!($oAccount = BxDolAccount::getInstance($aUser['email'])))
  46. continue;
  47. if (!$oAccount->isConfirmedEmail() && $oAccount->updateEmailConfirmed(true, false))
  48. $iConfirmed++;
  49. foreach ($oAccount->getProfiles(false) as $iProfileId => $aProfile)
  50. if ($aProfile['type'] == 'bx_persons')
  51. $aPersons[] = (int)$iProfileId;
  52. }
  53. $iFriends = 0;
  54. $oFriends = BxDolConnection::getObjectInstance('sys_profiles_friends');
  55. for ($i = 1; $i < count($aPersons); $i++) {
  56. [$iA, $iB] = [$aPersons[$i - 1], $aPersons[$i]];
  57. if ($oFriends->isConnected($iA, $iB, true))
  58. continue;
  59. $oFriends->addConnection($iA, $iB); // request
  60. $oFriends->addConnection($iB, $iA); // accept
  61. $iFriends++;
  62. }
  63. $iFollows = 0;
  64. $oFollows = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
  65. foreach ($aPersons as $i => $iA) {
  66. $iB = $aPersons[($i + 1) % count($aPersons)];
  67. if ($iA != $iB && !$oFollows->isConnected($iA, $iB) && $oFollows->addConnection($iA, $iB))
  68. $iFollows++;
  69. }
  70. // New connections queue a refresh themselves; a confirmation does not.
  71. if ($iConfirmed)
  72. foreach ($aPersons as $iProfileId)
  73. BxDolRecommendation::updateData($iProfileId);
  74. if ($iConfirmed || $iFriends || $iFollows)
  75. seedLog("people: $iConfirmed confirmed, $iFriends friendships, $iFollows follows");
  76. }
  77. if (BxDolAccount::getInstance($aUsers[0]['email'])) {
  78. seedPeople($aUsers);
  79. seedLog('demo content already present, skipping');
  80. exit(0);
  81. }
  82. $sPassword = getenv('UNA_DEMO_PASSWORD') ?: bin2hex(random_bytes(12));
  83. $oAccountForms = bx_instance('BxTemplAccountForms');
  84. // --- people: account + person profile each
  85. $aProfiles = []; // [account_id, person profile id]
  86. foreach ($aUsers as $aUser) {
  87. $a = $oAccountForms->createAccount([
  88. 'name' => $aUser['name'],
  89. 'email' => $aUser['email'],
  90. 'password' => $sPassword,
  91. ], BX_PROFILE_ACTION_AUTO, false);
  92. if (empty($a['account_id'])) {
  93. seedLog("account {$aUser['email']} failed: " . ($a['error'] ?? 'unknown'));
  94. continue;
  95. }
  96. $aFields = bx_srv('bx_persons', 'prepare_fields', [['author' => $a['profile_id'], 'name' => $aUser['name']]]);
  97. $aFields['allow_view_to'] = BX_DOL_PG_ALL;
  98. $r = bx_srv('bx_persons', 'entity_add_forcedly', [$a['profile_id'], $aFields]);
  99. if (empty($r['content']['profile_id'])) {
  100. seedLog("person {$aUser['name']} failed: " . ($r['message'] ?? 'unknown'));
  101. continue;
  102. }
  103. $aProfiles[] = [$a['account_id'], (int)$r['content']['profile_id']];
  104. seedLog("person {$aUser['name']}");
  105. }
  106. if (!$aProfiles) {
  107. seedLog('no profiles created, stopping');
  108. exit(1);
  109. }
  110. /** Act as a demo user, so ACL checks and authorship match a real post. */
  111. function seedAs(array $aProfile): int
  112. {
  113. bx_login($aProfile[0], false);
  114. return $aProfile[1];
  115. }
  116. // --- a public group (when the package has the Groups module)
  117. $iGroupProfileId = 0;
  118. $iOwner = seedAs($aProfiles[0]);
  119. $r = !BxDolModuleQuery::getInstance()->isEnabledByName('bx_groups') ? ['message' => 'bx_groups is not installed'] : bx_srv('bx_groups', 'entity_add_forcedly', [$iOwner, [
  120. 'group_name' => 'UNA Community',
  121. 'group_desc' => 'Introduce yourself, share what you are building with UNA and NEO, and ask questions.',
  122. 'allow_view_to' => BX_DOL_PG_ALL,
  123. // Participants may post (the privacy object's default). Left out, the forced add
  124. // stored '' (seen on api.unacms.app), which no privacy group matches.
  125. 'allow_post_to' => 'p',
  126. ]]);
  127. if (!empty($r['content']['profile_id'])) {
  128. $iGroupProfileId = (int)$r['content']['profile_id'];
  129. seedLog('group UNA Community');
  130. }
  131. else
  132. seedLog('group skipped: ' . ($r['message'] ?? 'unknown'));
  133. // --- posts, each with a couple of comments from other people
  134. $aPosts = [
  135. ['Hello from the demo site', "This site is a fresh UNA install deployed from the master branch. Everything here is sample content."],
  136. ['What NEO is', "NEO is the universal UNA client: one React Native codebase for web, iOS and Android, talking to UNA through its API."],
  137. ['Preview environments', "Every pull request to UNA gets its own backend and client on Railway, created by CI and removed when the PR closes."],
  138. ['Weekend reading', "A few articles about building communities that stay useful: keep threads short, answer quickly, and welcome new members by name."],
  139. ['Photo walk this Saturday', "Meeting at the old harbour at 10:00. Bring any camera; phones are fine. Coffee afterwards."],
  140. ['Tips for a good profile', "Add a picture, write two sentences about what you do, and follow a few groups. That's enough to get a useful feed."],
  141. ];
  142. $aComments = [
  143. 'Great to see this running.',
  144. 'Thanks for sharing!',
  145. 'Count me in.',
  146. 'Very helpful, bookmarking this.',
  147. ];
  148. foreach ($aPosts as $i => [$sTitle, $sText]) {
  149. $aAuthor = $aProfiles[$i % count($aProfiles)];
  150. $iAuthor = seedAs($aAuthor);
  151. $r = bx_srv('bx_posts', 'entity_add_forcedly', [$iAuthor, [
  152. 'title' => $sTitle,
  153. 'text' => '<p>' . $sText . '</p>',
  154. 'allow_view_to' => BX_DOL_PG_ALL,
  155. ]]);
  156. if (empty($r['content']['id'])) {
  157. seedLog("post '$sTitle' failed: " . ($r['message'] ?? 'unknown'));
  158. continue;
  159. }
  160. seedLog("post '$sTitle'");
  161. if (!($oCmts = BxDolCmts::getObjectInstance('bx_posts', (int)$r['content']['id'])))
  162. continue;
  163. for ($j = 1; $j <= 2; $j++) {
  164. $aCommenter = $aProfiles[($i + $j) % count($aProfiles)];
  165. $oCmts->add([
  166. 'cmt_author_id' => seedAs($aCommenter),
  167. 'cmt_parent_id' => 0,
  168. 'cmt_text' => $aComments[($i + $j) % count($aComments)],
  169. ]);
  170. }
  171. }
  172. seedPeople($aUsers);
  173. seedLog('done: ' . count($aProfiles) . ' people, ' . ($iGroupProfileId ? 1 : 0) . ' group, ' . count($aPosts) . ' posts');
  174. ob_end_clean();