├── .dockerignore ├── .env.docker-compose.dev ├── .env.docker-compose.test ├── .github ├── ISSUE_TEMPLATE │ ├── bug-issue.md │ └── feature-issue.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── awsdeploy.yml │ ├── build-docs.yml │ ├── ci.yml │ ├── deploy-template.yml │ ├── e2e.yml │ ├── ecrbuild-all.yml │ ├── ecrbuild-template.yml │ ├── on_main.yml │ ├── on_pr.yml │ ├── pull-preview-script.sh │ └── pull-preview.yml ├── .gitignore ├── .husky ├── pre-commit └── pre-push ├── .npmrc ├── .nvmrc ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── DEVELOPMENT.md ├── Dockerfile ├── LICENSE ├── README.md ├── babel.config.js ├── bin └── render-build.sh ├── config ├── eslint │ ├── base.js │ ├── eslint.config.mjs │ ├── next.js │ ├── package.json │ ├── react.js │ ├── tsconfig.json │ └── types.d.ts ├── prettier │ ├── index.js │ ├── package.json │ └── tsconfig.json └── tsconfig │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json ├── core ├── .env.development ├── .env.docker ├── .env.template ├── .env.test ├── .github │ └── workflows │ │ └── playwright.yml ├── .gitignore ├── .prettierignore ├── README.md ├── actions │ ├── _lib │ │ ├── custom-form-field │ │ │ ├── defineConfigServerComponent.tsx │ │ │ ├── defineFormField.tsx │ │ │ ├── getCustomConfigComponent.tsx │ │ │ └── resolveFieldConfig.tsx │ │ ├── getRuns.ts │ │ ├── resolvePubfields.ts │ │ ├── rules.ts │ │ ├── runActionInstance.db.test.ts │ │ ├── runActionInstance.ts │ │ ├── scheduleActionInstance.ts │ │ └── zodTypes.ts │ ├── api │ │ ├── client.ts │ │ ├── index.ts │ │ ├── server.ts │ │ └── serverAction.ts │ ├── datacite │ │ ├── action.ts │ │ ├── run.test.ts │ │ ├── run.ts │ │ └── types.ts │ ├── email │ │ ├── action.ts │ │ ├── config │ │ │ └── recipientMember.field.tsx │ │ ├── params │ │ │ └── recipientMember.field.tsx │ │ └── run.ts │ ├── googleDriveImport │ │ ├── OutputField.tsx │ │ ├── action.ts │ │ ├── config │ │ │ └── outputField.field.tsx │ │ ├── discussionSchema.ts │ │ ├── formatDriveData.ts │ │ ├── gdocPlugins.test.ts │ │ ├── gdocPlugins.ts │ │ ├── getGDriveFiles.ts │ │ ├── params │ │ │ └── outputField.field.tsx │ │ └── run.ts │ ├── http │ │ ├── action.ts │ │ ├── config │ │ │ ├── client-components │ │ │ │ └── FieldOutputMap.tsx │ │ │ └── outputMap.field.tsx │ │ ├── params │ │ │ └── outputMap.field.tsx │ │ └── run.ts │ ├── log │ │ ├── action.ts │ │ └── run.ts │ ├── move │ │ ├── action.ts │ │ ├── config │ │ │ └── stage.field.tsx │ │ └── run.ts │ ├── pdf │ │ ├── action.ts │ │ └── run.ts │ ├── pushToV6 │ │ ├── action.ts │ │ └── run.ts │ ├── runs.ts │ └── types.ts ├── app │ ├── (user) │ │ ├── UserStyles.module.css │ │ ├── communities │ │ │ ├── AddCommunityDialog.tsx │ │ │ ├── AddCommunityForm.tsx │ │ │ ├── CommunityTable.tsx │ │ │ ├── RemoveCommunityButton.tsx │ │ │ ├── actions.ts │ │ │ ├── getCommunityTableColumns.tsx │ │ │ └── page.tsx │ │ ├── confirm │ │ │ └── page.tsx │ │ ├── forgot │ │ │ ├── ForgotForm.tsx │ │ │ └── page.tsx │ │ ├── invalid-token │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── login │ │ │ ├── LoginForm.tsx │ │ │ └── page.tsx │ │ ├── magic-link │ │ │ └── route.ts │ │ ├── reset │ │ │ ├── ResetForm.tsx │ │ │ └── page.tsx │ │ ├── settings │ │ │ ├── ResetPasswordButton.tsx │ │ │ ├── UserInfoForm.tsx │ │ │ ├── actions.ts │ │ │ └── page.tsx │ │ ├── signup │ │ │ └── page.tsx │ │ └── verify │ │ │ ├── ResendVerificationButton.tsx │ │ │ └── page.tsx │ ├── RootToaster.tsx │ ├── api │ │ ├── health │ │ │ └── route.ts │ │ └── v0 │ │ │ └── c │ │ │ └── [communitySlug] │ │ │ ├── internal │ │ │ └── [...ts-rest] │ │ │ │ └── route.ts │ │ │ ├── site │ │ │ └── [...ts-rest] │ │ │ │ └── route.ts │ │ │ └── sse │ │ │ └── route.ts │ ├── apple-icon.png │ ├── c │ │ ├── (public) │ │ │ └── [communitySlug] │ │ │ │ ├── layout.tsx │ │ │ │ └── public │ │ │ │ ├── Header.tsx │ │ │ │ ├── forms │ │ │ │ └── [formSlug] │ │ │ │ │ └── fill │ │ │ │ │ ├── ExternalFormWrapper.tsx │ │ │ │ │ ├── RequestLink.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── utils.ts │ │ │ │ ├── invite │ │ │ │ ├── AcceptRejectInvite.tsx │ │ │ │ ├── InviteStatuses.tsx │ │ │ │ ├── WrongUserLoggedIn.tsx │ │ │ │ ├── actions.ts │ │ │ │ └── page.tsx │ │ │ │ └── signup │ │ │ │ └── page.tsx │ │ └── [communitySlug] │ │ │ ├── CommunitySwitcher.tsx │ │ │ ├── ContentLayout.tsx │ │ │ ├── LoginSwitcher.tsx │ │ │ ├── NavLink.tsx │ │ │ ├── NavLinkSubMenu.tsx │ │ │ ├── SideNav.tsx │ │ │ ├── activity │ │ │ └── actions │ │ │ │ ├── ActionRunsTable.tsx │ │ │ │ ├── getActionRunsTableColumns.tsx │ │ │ │ └── page.tsx │ │ │ ├── developers │ │ │ └── docs │ │ │ │ ├── openapi.json │ │ │ │ ├── openApi.ts │ │ │ │ └── route.ts │ │ │ │ ├── page.tsx │ │ │ │ └── stoplight.styles.css │ │ │ ├── fields │ │ │ ├── FieldForm.tsx │ │ │ ├── FieldsTable.tsx │ │ │ ├── NewFieldButton.tsx │ │ │ ├── actions.ts │ │ │ ├── getFieldTableColumns.tsx │ │ │ └── page.tsx │ │ │ ├── forms │ │ │ ├── FormTable.tsx │ │ │ ├── NewFormButton.tsx │ │ │ ├── [formSlug] │ │ │ │ └── edit │ │ │ │ │ ├── EditFormTitleButton.tsx │ │ │ │ │ ├── FormCopyButton.tsx │ │ │ │ │ ├── actions.ts │ │ │ │ │ └── page.tsx │ │ │ ├── actions.ts │ │ │ ├── getFormTableColumns.tsx │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── members │ │ │ ├── MemberTable.tsx │ │ │ ├── RemoveMemberButton.tsx │ │ │ ├── actions.ts │ │ │ ├── getMemberTableColumns.tsx │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ ├── pubs │ │ │ ├── PubHeader.tsx │ │ │ ├── PubList.tsx │ │ │ ├── [pubId] │ │ │ │ ├── actions.ts │ │ │ │ ├── components │ │ │ │ │ ├── PubValues.tsx │ │ │ │ │ ├── RelatedPubsTable.tsx │ │ │ │ │ ├── RelatedPubsTableWrapper.tsx │ │ │ │ │ └── __tests__ │ │ │ │ │ │ ├── PubValues.test.tsx │ │ │ │ │ │ ├── article-pub-fixture.json │ │ │ │ │ │ └── author-pub-fixture.json │ │ │ │ ├── edit │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── create │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ │ ├── search │ │ │ └── page.tsx │ │ │ ├── settings │ │ │ ├── page.tsx │ │ │ └── tokens │ │ │ │ ├── CreateTokenForm.tsx │ │ │ │ ├── CreateTokenFormContext.tsx │ │ │ │ ├── ExistingToken.tsx │ │ │ │ ├── PermissionField.tsx │ │ │ │ ├── RevokeTokenButton.tsx │ │ │ │ ├── actions.ts │ │ │ │ └── page.tsx │ │ │ ├── stages │ │ │ ├── [stageId] │ │ │ │ └── page.tsx │ │ │ ├── components │ │ │ │ ├── Move.tsx │ │ │ │ ├── StageList.tsx │ │ │ │ ├── StagePubActions.tsx │ │ │ │ └── lib │ │ │ │ │ └── actions.ts │ │ │ ├── manage │ │ │ │ ├── StagesContext.tsx │ │ │ │ ├── actions.ts │ │ │ │ ├── components │ │ │ │ │ ├── editor │ │ │ │ │ │ ├── StageEditor.tsx │ │ │ │ │ │ ├── StageEditorContext.tsx │ │ │ │ │ │ ├── StageEditorContextMenu.tsx │ │ │ │ │ │ ├── StageEditorKeyboardControls.tsx │ │ │ │ │ │ ├── StageEditorMenubar.tsx │ │ │ │ │ │ └── StageEditorNode.tsx │ │ │ │ │ └── panel │ │ │ │ │ │ ├── StageNameInput.tsx │ │ │ │ │ │ ├── StagePanel.tsx │ │ │ │ │ │ ├── StagePanelMembers.tsx │ │ │ │ │ │ ├── StagePanelOverview.tsx │ │ │ │ │ │ ├── StagePanelOverviewManagement.tsx │ │ │ │ │ │ ├── StagePanelPubs.tsx │ │ │ │ │ │ ├── StagePanelSheet.tsx │ │ │ │ │ │ └── actionsTab │ │ │ │ │ │ ├── StagePanelActionCreator.tsx │ │ │ │ │ │ ├── StagePanelActionEditor.tsx │ │ │ │ │ │ ├── StagePanelActions.tsx │ │ │ │ │ │ ├── StagePanelRule.tsx │ │ │ │ │ │ ├── StagePanelRuleCreator.tsx │ │ │ │ │ │ └── StagePanelRules.tsx │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ │ ├── types │ │ │ ├── CreatePubType.tsx │ │ │ ├── FieldSelect.tsx │ │ │ ├── RemoveFieldButton.tsx │ │ │ ├── TypeBlock.tsx │ │ │ ├── TypeEditor.tsx │ │ │ ├── TypeList.tsx │ │ │ ├── actions.ts │ │ │ ├── page.tsx │ │ │ └── utils.ts │ │ │ └── unauthorized │ │ │ └── page.tsx │ ├── components │ │ ├── ActionUI │ │ │ ├── ActionConfigForm.tsx │ │ │ ├── ActionConfigFormWrapper.tsx │ │ │ ├── ActionRunForm.tsx │ │ │ ├── ActionRunFormWrapper.tsx │ │ │ ├── PubsRunActionDropDownMenu.tsx │ │ │ └── PubsRunActionDropDownMenuItem.tsx │ │ ├── ActiveArchiveTabs.tsx │ │ ├── ContextEditor │ │ │ ├── AtomRenderer.tsx │ │ │ ├── ContextEditorClient.tsx │ │ │ └── ContextEditorContext.tsx │ │ ├── CopyCurrentUrlButton.tsx │ │ ├── CreateEditDialog.tsx │ │ ├── DataTable │ │ │ ├── DataTable.tsx │ │ │ ├── PubsDataTable │ │ │ │ ├── PubsDataTableClient.tsx │ │ │ │ ├── PubsDataTableServer.tsx │ │ │ │ ├── columns.tsx │ │ │ │ └── validations.ts │ │ │ └── v2 │ │ │ │ └── DataTable.tsx │ │ ├── FormBuilder │ │ │ ├── ArchiveFormButton.tsx │ │ │ ├── ElementPanel │ │ │ │ ├── ButtonConfigurationForm.tsx │ │ │ │ ├── ComponentConfig │ │ │ │ │ ├── Checkbox.tsx │ │ │ │ │ ├── CheckboxGroup.tsx │ │ │ │ │ ├── ColorPicker.tsx │ │ │ │ │ ├── ConfidenceInterval.tsx │ │ │ │ │ ├── DatePicker.tsx │ │ │ │ │ ├── FileUpload.tsx │ │ │ │ │ ├── MemberSelect.tsx │ │ │ │ │ ├── MultivalueBase.tsx │ │ │ │ │ ├── MultivalueInput.tsx │ │ │ │ │ ├── RadioGroup.tsx │ │ │ │ │ ├── RelationBlock.tsx │ │ │ │ │ ├── RichText.tsx │ │ │ │ │ ├── SelectDropdown.tsx │ │ │ │ │ ├── TextArea.tsx │ │ │ │ │ ├── TextInput.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── types.ts │ │ │ │ ├── InputComponentConfigurationForm.tsx │ │ │ │ ├── SelectAccess.tsx │ │ │ │ ├── SelectElement.tsx │ │ │ │ ├── StructuralElementConfigurationForm.tsx │ │ │ │ └── index.tsx │ │ │ ├── FieldIcon.tsx │ │ │ ├── FormBuilder.tsx │ │ │ ├── FormBuilderContext.tsx │ │ │ ├── FormElement.tsx │ │ │ ├── FormName.tsx │ │ │ ├── FormPreview.tsx │ │ │ ├── RestoreFormButton.tsx │ │ │ ├── SaveFormButton.tsx │ │ │ ├── StructuralElements.tsx │ │ │ ├── SubmissionSettings.tsx │ │ │ ├── actions.ts │ │ │ ├── types.ts │ │ │ └── useIsChanged.tsx │ │ ├── LastVisitedCommunity │ │ │ ├── SetLastVisited.tsx │ │ │ └── constants.ts │ │ ├── Logo.tsx │ │ ├── LogoutButton.tsx │ │ ├── MemberSelect │ │ │ ├── MemberSelectAddUserButton.tsx │ │ │ ├── MemberSelectAddUserForm.tsx │ │ │ ├── MemberSelectClient.tsx │ │ │ ├── MemberSelectClientFetch.tsx │ │ │ └── types.ts │ │ ├── Memberships │ │ │ ├── AddMemberDialog.tsx │ │ │ ├── MemberInviteForm.tsx │ │ │ ├── MembersList.tsx │ │ │ ├── RemoveMemberButton.tsx │ │ │ ├── RoleSelect.tsx │ │ │ ├── memberInviteFormSchema.ts │ │ │ └── types.ts │ │ ├── Notice.tsx │ │ ├── Pagination.tsx │ │ ├── PathAwareDialog.tsx │ │ ├── PubRow.tsx │ │ ├── PubTitle.tsx │ │ ├── Row.tsx │ │ ├── SearchDialog.tsx │ │ ├── SidePanel.tsx │ │ ├── Signup │ │ │ ├── JoinCommunityForm.tsx │ │ │ ├── SignupForm.tsx │ │ │ └── schema.ts │ │ ├── StageSelect │ │ │ ├── StageSelectClient.tsx │ │ │ └── StageSelectServer.tsx │ │ ├── SubmitButton.tsx │ │ ├── TableActionMenu.tsx │ │ ├── UserAvatar.tsx │ │ ├── UserCard.tsx │ │ ├── __tests__ │ │ │ ├── CheckboxGroupElement.test.tsx │ │ │ ├── DataTableV2.test.tsx │ │ │ ├── PubTitle.test.tsx │ │ │ ├── RadioGroupElement.test.tsx │ │ │ └── SelectDropdownElement.test.tsx │ │ ├── forms │ │ │ ├── AddRelatedPubsPanel.tsx │ │ │ ├── FileUpload.tsx │ │ │ ├── FormElement.tsx │ │ │ ├── FormElementToggle.tsx │ │ │ ├── FormElementToggleContext.tsx │ │ │ ├── PubFieldFormElement.tsx │ │ │ ├── actions.ts │ │ │ ├── elements │ │ │ │ ├── CheckboxElement.tsx │ │ │ │ ├── CheckboxGroupElement.tsx │ │ │ │ ├── ColorPickerElement.tsx │ │ │ │ ├── ConfidenceElement.tsx │ │ │ │ ├── ContextEditorElement.tsx │ │ │ │ ├── DateElement.tsx │ │ │ │ ├── FileUploadElement.tsx │ │ │ │ ├── MemberSelectElement.tsx │ │ │ │ ├── MultivalueInputElement.tsx │ │ │ │ ├── RadioGroupElement.tsx │ │ │ │ ├── RelatedPubsElement.tsx │ │ │ │ ├── SelectDropdownElement.tsx │ │ │ │ ├── TextAreaElement.tsx │ │ │ │ └── TextInputElement.tsx │ │ │ ├── structural.ts │ │ │ └── types.ts │ │ ├── providers │ │ │ ├── CommunityProvider.tsx │ │ │ └── QueryProvider.tsx │ │ ├── pubs │ │ │ ├── CreatePubButton.tsx │ │ │ ├── InitialCreatePubForm.tsx │ │ │ ├── PubDropDown.tsx │ │ │ ├── PubEditor │ │ │ │ ├── PageTitleWithStatus.tsx │ │ │ │ ├── PubEditor.tsx │ │ │ │ ├── PubEditorClient.tsx │ │ │ │ ├── PubEditorWrapper.tsx │ │ │ │ ├── SaveStatus.tsx │ │ │ │ ├── SubmitButtons.tsx │ │ │ │ ├── actions.db.test.ts │ │ │ │ ├── actions.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── helpers.ts │ │ │ │ └── queries.ts │ │ │ ├── RemovePubButton.tsx │ │ │ ├── RemovePubForm.tsx │ │ │ └── RemovePubFormClient.tsx │ │ └── skeletons │ │ │ ├── SkeletonButton.tsx │ │ │ ├── SkeletonCard.tsx │ │ │ └── SkeletonTable.tsx │ ├── favicon.ico │ ├── global-error.tsx │ ├── globals.css │ ├── icon.svg │ ├── layout.tsx │ ├── page.tsx │ ├── pubs │ │ └── [pubId] │ │ │ └── page.tsx │ └── robots.txt ├── cache-handler.mjs ├── docs │ ├── load-testing.md │ └── pub-relationships.md ├── eslint.config.mjs ├── instrumentation.edge.mts ├── instrumentation.node.mts ├── instrumentation.onRequestError.ts ├── instrumentation.ts ├── kysely │ ├── artificial-latency-plugin.ts │ ├── database-init.ts │ ├── database.ts │ ├── errors.ts │ ├── updated-at-plugin.test.ts │ └── updated-at-plugin.ts ├── lib │ ├── __tests__ │ │ ├── db.ts │ │ ├── fixtures │ │ │ └── big-croc.jpg │ │ ├── globalSetup.ts │ │ ├── live.db.test.ts │ │ ├── matchers.ts │ │ ├── pubs.test.ts │ │ ├── richText.test.ts │ │ ├── transactions.ts │ │ ├── utils.ts │ │ └── validateFields.test.ts │ ├── api.ts │ ├── authentication │ │ ├── README.md │ │ ├── actions.ts │ │ ├── api.ts │ │ ├── cookies.ts │ │ ├── createMagicLink.ts │ │ ├── errors.ts │ │ ├── loginData.ts │ │ ├── lucia.ts │ │ ├── password.ts │ │ └── roles.ts │ ├── authorization │ │ ├── capabilities.db.test.ts │ │ ├── capabilities.ts │ │ └── rolesRanking.ts │ ├── dates.ts │ ├── db │ │ └── queries.ts │ ├── editor │ │ ├── process-editor-html.ts │ │ ├── serialize-server.ts │ │ └── to-html.test.ts │ ├── env │ │ ├── env.ts │ │ └── flags.ts │ ├── fields │ │ ├── fileUpload.ts │ │ ├── richText.ts │ │ └── utils.ts │ ├── form.ts │ ├── lastModifiedBy.ts │ ├── logging.ts │ ├── mapping.ts │ ├── notify │ │ ├── SSERevalidator.tsx │ │ └── useSSEUpdates.ts │ ├── pubs.db.test.ts │ ├── pubs.ts │ ├── rank.ts │ ├── redirect.ts │ ├── redis.ts │ ├── server │ │ ├── __snapshots__ │ │ │ └── pub-filters.db.test.ts.snap │ │ ├── actions.ts │ │ ├── apiAccessTokens.ts │ │ ├── assets.db.test.ts │ │ ├── assets.ts │ │ ├── cache │ │ │ ├── README.md │ │ │ ├── autoCache.ts │ │ │ ├── autoRevalidate.ts │ │ │ ├── cache.test.ts │ │ │ ├── cacheTags.ts │ │ │ ├── constants.ts │ │ │ ├── getCommunitySlug.ts │ │ │ ├── memoize.ts │ │ │ ├── revalidate.ts │ │ │ ├── sharedAuto.ts │ │ │ ├── specialTables.ts │ │ │ └── types.ts │ │ ├── community.ts │ │ ├── defineServerAction.ts │ │ ├── email.tsx │ │ ├── errors.ts │ │ ├── form.ts │ │ ├── index.ts │ │ ├── invites │ │ │ ├── InviteBuilder.db.test.ts │ │ │ ├── InviteBuilder.ts │ │ │ ├── InviteService.ts │ │ │ └── helpers.ts │ │ ├── jobs.ts │ │ ├── mailgun.ts │ │ ├── maybeWithTrx.ts │ │ ├── member.ts │ │ ├── navigation │ │ │ └── redirects.ts │ │ ├── pub-capabilities.db.test.ts │ │ ├── pub-filters-validate.ts │ │ ├── pub-filters.db.test.ts │ │ ├── pub-filters.ts │ │ ├── pub-op.db.test.ts │ │ ├── pub-op.ts │ │ ├── pub-trigger.db.test.ts │ │ ├── pub.db.test.ts │ │ ├── pub.fts.db.test.ts │ │ ├── pub.sort.db.test.ts │ │ ├── pub.ts │ │ ├── pubFields.ts │ │ ├── pubtype.ts │ │ ├── render │ │ │ └── pub │ │ │ │ ├── renderMarkdownWithPub.ts │ │ │ │ ├── renderWithPubTokens.ts │ │ │ │ └── renderWithPubUtils.ts │ │ ├── rules.db.test.ts │ │ ├── rules.ts │ │ ├── stages.db.test.ts │ │ ├── stages.ts │ │ ├── token.ts │ │ ├── user.ts │ │ ├── validateFields.ts │ │ └── vitest.d.ts │ ├── serverActions.ts │ ├── stages.test.ts │ ├── stages.ts │ ├── string.ts │ ├── types.ts │ └── ui.ts ├── load-test-flows.ts ├── load-test.yaml ├── middleware.ts ├── next.config.ts ├── next.docker.config.js ├── package.json ├── playwright.config.ts ├── playwright │ ├── actions.config.spec.ts │ ├── actions.rules.spec.ts │ ├── actions.sse.spec.ts │ ├── api │ │ └── site.spec.ts │ ├── email.spec.ts │ ├── externalFormCreatePub.spec.ts │ ├── externalFormInvite.spec.ts │ ├── externalFormWithPubId.spec.ts │ ├── fields.spec.ts │ ├── fileUpload.spec.ts │ ├── fixtures │ │ ├── api-token-page.ts │ │ ├── community-page.ts │ │ ├── fields-page.ts │ │ ├── forms-edit-page.ts │ │ ├── forms-page.ts │ │ ├── login-page.ts │ │ ├── member-page.ts │ │ ├── password-reset-page.ts │ │ ├── pub-details-page.ts │ │ ├── pub-types-page.ts │ │ ├── pubs-page.ts │ │ ├── stages-manage-page.ts │ │ └── test-assets │ │ │ └── test-diagram.png │ ├── formAccess.spec.ts │ ├── formBuilder.spec.ts │ ├── helpers.ts │ ├── inbucketClient.ts │ ├── invites.spec.ts │ ├── login.flows.ts │ ├── login.spec.ts │ ├── member.spec.ts │ ├── pub.spec.ts │ ├── pubType.spec.ts │ ├── site-api.spec.ts │ └── verifyEmail.spec.ts ├── postcss.config.cjs ├── prisma │ ├── create-admin-user.cts │ ├── migrations │ │ ├── 20230614132455_init_user │ │ │ └── migration.sql │ │ ├── 20230615122708_placeholders │ │ │ └── migration.sql │ │ ├── 20230621173054_pub_schema_pass_1 │ │ │ └── migration.sql │ │ ├── 20230621173228_community_name │ │ │ └── migration.sql │ │ ├── 20230621174338_fix_pub_parent_required │ │ │ └── migration.sql │ │ ├── 20230621175305_add_community_to_pub │ │ │ └── migration.sql │ │ ├── 20230622182329_metadata_refactor │ │ │ └── migration.sql │ │ ├── 20230622182626_blob_optional │ │ │ └── migration.sql │ │ ├── 20230622194921_workflow_init │ │ │ └── migration.sql │ │ ├── 20230622195031_workflows_to_community │ │ │ └── migration.sql │ │ ├── 20230622195558_stage_names_order │ │ │ └── migration.sql │ │ ├── 20230705201158_refactor_fields │ │ │ └── migration.sql │ │ ├── 20230706134853_instances │ │ │ └── migration.sql │ │ ├── 20230706152501_integegration_field_ids │ │ │ └── migration.sql │ │ ├── 20230710105851_instance_community │ │ │ └── migration.sql │ │ ├── 20230712173822_community_avatar │ │ │ └── migration.sql │ │ ├── 20230712174327_pin_schema │ │ │ └── migration.sql │ │ ├── 20230713124028_type_description │ │ │ └── migration.sql │ │ ├── 20230730130805_remove_workflowws │ │ │ └── migration.sql │ │ ├── 20230730131013_really_drop_workflows │ │ │ └── migration.sql │ │ ├── 20230730141617_community_slug_first │ │ │ └── migration.sql │ │ ├── 20230730141652_community_slug_final │ │ │ └── migration.sql │ │ ├── 20230906210115_add_auth_tokens │ │ │ └── migration.sql │ │ ├── 20230907211557_zero_to_one_stages_per_integration_instance │ │ │ └── migration.sql │ │ ├── 20230913200757_add_pub_hierarchy │ │ │ └── migration.sql │ │ ├── 20230919171747_add_pubfieldschema │ │ │ └── migration.sql │ │ ├── 20230925191147_add_pubfieldschema_dates │ │ │ └── migration.sql │ │ ├── 20230926234242_add_pubfield_slugs │ │ │ └── migration.sql │ │ ├── 20230928124422_user_name_first_last │ │ │ └── migration.sql │ │ ├── 20231010215802_add_supabase_id │ │ │ └── migration.sql │ │ ├── 20231012092908_pub_delete_cascade │ │ │ └── migration.sql │ │ ├── 20231016133737_nullable_last_name │ │ │ └── migration.sql │ │ ├── 20231030180206_add_config_column_to_schema │ │ │ └── migration.sql │ │ ├── 20231031164256_add_integration_instance_state_table │ │ │ └── migration.sql │ │ ├── 20231107143830_change_to_state │ │ │ └── migration.sql │ │ ├── 20240117220109_add_unique_constraint_on_move_constraints │ │ │ └── migration.sql │ │ ├── 20240117220908_create_compound_move_constraint_id │ │ │ └── migration.sql │ │ ├── 20240306184411_actions │ │ │ └── migration.sql │ │ ├── 20240307023916_move_constraint_ondelete_cascade │ │ │ └── migration.sql │ │ ├── 20240314025159_add_assignee_column │ │ │ └── migration.sql │ │ ├── 20240318203453_add_action_instance │ │ │ └── migration.sql │ │ ├── 20240328181858_add_action_description │ │ │ └── migration.sql │ │ ├── 20240403211924_add_action_instance_config │ │ │ └── migration.sql │ │ ├── 20240410194529_add_is_super_admin_to_user_model │ │ │ └── migration.sql │ │ ├── 20240410232644_add_explicit_pubs_to_stages_m_m_relationship │ │ │ └── migration.sql │ │ ├── 20240410233228_remove_implicit_pubs_to_stages_relationship │ │ │ └── migration.sql │ │ ├── 20240411012752_create_trigger_on_pub_stage_join_table │ │ │ └── migration.sql │ │ ├── 20240415174050_add_name_to_action_instances │ │ │ └── migration.sql │ │ ├── 20240415185449_remove_action_table │ │ │ └── migration.sql │ │ ├── 20240416111211_drop_actions_pubfields │ │ │ └── migration.sql │ │ ├── 20240416124817_set_postgres_defaults_for_uuids │ │ │ └── migration.sql │ │ ├── 20240416211436_add_push_to_v6_action │ │ │ └── migration.sql │ │ ├── 20240416214541_add_rules │ │ │ └── migration.sql │ │ ├── 20240416234734_add_cascade_on_delete │ │ │ └── migration.sql │ │ ├── 20240418185908_remove_pubs_in_stages_if_pubs_or_stages_are_removed │ │ │ └── migration.sql │ │ ├── 20240422152704_add_http_action │ │ │ └── migration.sql │ │ ├── 20240506184830_add_move_action │ │ │ └── migration.sql │ │ ├── 20240520190654_add_action_runs │ │ │ └── migration.sql │ │ ├── 20240523104802_add_new_duration_event │ │ │ └── migration.sql │ │ ├── 20240523122445_add_config_option_for_rules │ │ │ └── migration.sql │ │ ├── 20240523130939_update_comments │ │ │ └── migration.sql │ │ ├── 20240529145416_add_forms │ │ │ └── migration.sql │ │ ├── 20240529160343_record_action_rsult │ │ │ └── migration.sql │ │ ├── 20240530103008_update_comments │ │ │ └── migration.sql │ │ ├── 20240530112116_add_scheduled_action_run_status │ │ │ └── migration.sql │ │ ├── 20240603202647_update_comments │ │ │ └── migration.sql │ │ ├── 20240604094500_transition_to_camelcase │ │ │ └── migration.sql │ │ ├── 20240604102349_update_comments │ │ │ └── migration.sql │ │ ├── 20240604114027_update_comments │ │ │ └── migration.sql │ │ ├── 20240617120000_update_pub_moved_trigger_to_include_community_slug │ │ │ └── migration.sql │ │ ├── 20240702114658_add_schemaname_column_to_pubfield │ │ │ └── migration.sql │ │ ├── 20240703161852_add_api_access_tokens │ │ │ └── migration.sql │ │ ├── 20240703161854_update_comments │ │ │ └── migration.sql │ │ ├── 20240709044026_add_forms_archiving │ │ │ └── migration.sql │ │ ├── 20240710022410_add_forms_slug_and_unique_constraint │ │ │ └── migration.sql │ │ ├── 20240711145906_add_is_archived_to_pubfields │ │ │ └── migration.sql │ │ ├── 20240711171041_update_comments │ │ │ └── migration.sql │ │ ├── 20240715133139_add_member_role_column │ │ │ └── migration.sql │ │ ├── 20240715133257_remove_can_admin_column │ │ │ └── migration.sql │ │ ├── 20240717105403_add_role_to_membergroup │ │ │ └── migration.sql │ │ ├── 20240717105614_remove_can_admin_column_on_membergroup │ │ │ └── migration.sql │ │ ├── 20240722140326_add_explicit_relation_between_forms_and_permissions_and_add_constraint_that_forces_xor_on_memberid_and_membergroup_id_on_permissions │ │ │ └── migration.sql │ │ ├── 20240722185836_add_community_id_to_pubfields │ │ │ └── migration.sql │ │ ├── 20240724231044_update_form_elements │ │ │ └── migration.sql │ │ ├── 20240725202915_enforce_unique_user_community_columns_in_members_table │ │ │ └── migration.sql │ │ ├── 20240729202132_user_id_to_member_id │ │ │ └── migration.sql │ │ ├── 20240731145045_remove_old_references_in_schemas │ │ │ └── migration.sql │ │ ├── 20240806164513_add_password_hash_and_salt_to_users_table │ │ │ └── migration.sql │ │ ├── 20240806170730_add_sessions_table │ │ │ └── migration.sql │ │ ├── 20240806170732_update_comments │ │ │ └── migration.sql │ │ ├── 20240806183507_support_buttons_in_form_element │ │ │ └── migration.sql │ │ ├── 20240819104844_add_authtokentype_to_both_session_and_authtoken │ │ │ └── migration.sql │ │ ├── 20240821101734_update_comments │ │ │ └── migration.sql │ │ ├── 20240829135909_rename_unjournal_fields │ │ │ └── migration.sql │ │ ├── 20240829182113_remove_supabase_id │ │ │ └── migration.sql │ │ ├── 20240905145651_component_configuration │ │ │ └── migration.sql │ │ ├── 20240917200221_add_relationships_to_pub_values │ │ │ └── migration.sql │ │ ├── 20240923161145_add_null_core_schema_type_and_make_pubvalues_unique_constraint_not_null_unique │ │ │ └── migration.sql │ │ ├── 20240930150006_add_number_numeric_string_array │ │ │ └── migration.sql │ │ ├── 20241002220742_remove_label_column_from_inputs │ │ │ └── migration.sql │ │ ├── 20241007162426_drop_history_tables_and_add_cascades │ │ │ └── migration.sql │ │ ├── 20241007162428_update_comments │ │ │ └── migration.sql │ │ ├── 20241007212932_add_more_options_to_input_component_enum │ │ │ └── migration.sql │ │ ├── 20241016174257_add_multivalue_input_to_input_component_enum │ │ │ └── migration.sql │ │ ├── 20241017223037_add_new_membership_tables │ │ │ └── migration.sql │ │ ├── 20241017223040_update_comments │ │ │ └── migration.sql │ │ ├── 20241021151230_add_rich_text_type │ │ │ └── migration.sql │ │ ├── 20241030175616_add_is_title_to_pubfield_pubtype_join_table │ │ │ └── migration.sql │ │ ├── 20241030175617_update_comments │ │ │ └── migration.sql │ │ ├── 20241105184158_copy_membership_data │ │ │ └── migration.sql │ │ ├── 20241106060651_delete_old_permissions_and_membership_tables │ │ │ └── migration.sql │ │ ├── 20241106060653_update_comments │ │ │ └── migration.sql │ │ ├── 20241107193617_add_capabilities_to_db │ │ │ └── migration.sql │ │ ├── 20241107193619_update_comments │ │ │ └── migration.sql │ │ ├── 20241126113759_add_pub_values_updated_at_trigger │ │ │ └── migration.sql │ │ ├── 20241126151624_add_google_drive_import_action │ │ │ └── migration.sql │ │ ├── 20241203035210_add_default_forms │ │ │ └── migration.sql │ │ ├── 20241203164958_add_base_history_fn │ │ │ └── migration.sql │ │ ├── 20241203193207_add_datacite_action │ │ │ └── migration.sql │ │ ├── 20241205152006_add_title_field │ │ │ └── migration.sql │ │ ├── 20241205192106_unique_pub_type_names │ │ │ └── migration.sql │ │ ├── 20241205231134_backfill_default_forms │ │ │ └── migration.sql │ │ ├── 20241210172906_trigger_for_changing_is_title │ │ │ └── migration.sql │ │ ├── 20241212130738_update_comments │ │ │ └── migration.sql │ │ ├── 20241212164758_fix_cascading_delete_issues │ │ │ └── migration.sql │ │ ├── 20241214173138_add_pub_values_history_history_table │ │ │ └── migration.sql │ │ ├── 20241214173204_update_comments │ │ │ └── migration.sql │ │ ├── 20241217171434_add_pubid_to_form_memberships │ │ │ └── migration.sql │ │ ├── 20241219131700_fix_title_update_trigger │ │ │ └── migration.sql │ │ ├── 20250114185819_rm_integrations │ │ │ └── migration.sql │ │ ├── 20250114190224_update_comments │ │ │ └── migration.sql │ │ ├── 20250114195812_add_relation_block_input_component │ │ │ └── migration.sql │ │ ├── 20250130165541_add_ts_vector_to_pub_values │ │ │ └── migration.sql │ │ ├── 20250203230851_add_updated_at_to_everything │ │ │ └── migration.sql │ │ ├── 20250205172301_stage_updated_at_trigger │ │ │ └── migration.sql │ │ ├── 20250213201642_add_mudder_ranks │ │ │ └── migration.sql │ │ ├── 20250227001152_let_community_editors_invite_users │ │ │ └── migration.sql │ │ ├── 20250304132049_add_action_success_events │ │ │ └── migration.sql │ │ ├── 20250305135055_add_action_ref_to_rul │ │ │ └── migration.sql │ │ ├── 20250306165526_log_triggering_action_run │ │ │ └── migration.sql │ │ ├── 20250306184110_remove_parent_id │ │ │ └── migration.sql │ │ ├── 20250313193528_add_see_extra_pub_values_capability │ │ │ └── migration.sql │ │ ├── 20250313200801_form_is_default_unique_constraint │ │ │ └── migration.sql │ │ ├── 20250319022702_remove_order │ │ │ └── migration.sql │ │ ├── 20250327190100_remove_invite_only_form_access_option │ │ │ └── migration.sql │ │ ├── 20250331203256_add_related_pub_types_to_form_elements │ │ │ └── migration.sql │ │ ├── 20250401134158_update_comments │ │ │ └── migration.sql │ │ ├── 20250402192249_add_form_id_to_all_memberships │ │ │ └── migration.sql │ │ ├── 20250403040948_remove_form_memberships │ │ │ └── migration.sql │ │ ├── 20250403040950_update_comments │ │ │ └── migration.sql │ │ ├── 20250407203301_add_is_verified_to_user │ │ │ └── migration.sql │ │ ├── 20250415174821_notify_change │ │ │ └── migration.sql │ │ ├── 20250416145507_drop_assignee │ │ │ └── migration.sql │ │ ├── 20250429103330_make_invite_table_more_clear │ │ │ └── migration.sql │ │ ├── 20250429103331_add_invites_history_history_table │ │ │ └── migration.sql │ │ ├── 20250429103332_update_comments │ │ │ └── migration.sql │ │ ├── 20250429153030_remove_email_column_from_invites │ │ │ └── migration.sql │ │ ├── 20250501133901_update_fts │ │ │ └── migration.sql │ │ ├── 20250512144047_clear_richtext_values │ │ │ └── migration.sql │ │ ├── 20250513181701_add_color_type │ │ │ └── migration.sql │ │ └── migration_lock.toml │ ├── schema │ │ ├── comments │ │ │ └── .comments-lock │ │ ├── history-tables │ │ │ ├── InviteHistory.prisma │ │ │ └── PubValueHistory.prisma │ │ ├── schema.dbml │ │ └── schema.prisma │ ├── scripts │ │ ├── comment-generator.mts │ │ └── history-tables │ │ │ ├── generate-history-table.mts │ │ │ └── history-table-migration-template.sql │ ├── seed.ts │ ├── seed │ │ ├── createSeed.ts │ │ ├── seed-wrapper.cts │ │ ├── seedCommunity.db.test.ts │ │ ├── seedCommunity.ts │ │ └── stubs │ │ │ ├── module-loader.js │ │ │ ├── register-loader.js │ │ │ └── stubs.js │ └── seeds │ │ ├── invite-test-community.ts │ │ ├── legacy.ts │ │ ├── ponies.snippet.html │ │ └── starter.ts ├── project.code-workspace ├── public │ ├── demo │ │ ├── arcadia.png │ │ ├── biorxiv.png │ │ ├── brown.png │ │ ├── croc.png │ │ ├── mitp.jpg │ │ ├── person.png │ │ └── unjournal.png │ ├── icons │ │ ├── chevron-vertical.svg │ │ ├── dashboard.svg │ │ ├── ellipsis.svg │ │ ├── form.svg │ │ ├── members.svg │ │ ├── pin.svg │ │ ├── pub.svg │ │ ├── search.svg │ │ ├── settings.svg │ │ └── stages.svg │ └── logos │ │ └── icon.svg ├── sentry.client.config.ts ├── tailwind.config.cjs ├── tsconfig.json ├── types │ └── preconstruct_next.ts └── vitest.config.mts ├── docker-compose.base.yml ├── docker-compose.dev.yml ├── docker-compose.preview.pr.yml ├── docker-compose.preview.sandbox.yml ├── docker-compose.preview.yml ├── docker-compose.test.yml ├── docs ├── .gitignore ├── README.md ├── content │ ├── _meta.ts │ ├── development │ │ ├── authentication │ │ │ └── invites.mdx │ │ ├── common-issues.mdx │ │ ├── db.mdx │ │ ├── getting-started.mdx │ │ ├── kysely.mdx │ │ ├── richtext.mdx │ │ └── serverActions.mdx │ ├── index.mdx │ └── infrastructure │ │ ├── ecs-cluster.mdx │ │ ├── environments │ │ ├── cloudflare.mdx │ │ └── global-aws.mdx │ │ ├── index.mdx │ │ ├── modules │ │ └── core-services.mdx │ │ └── nginx.mdx ├── eslint.config.mjs ├── mdx-components.tsx ├── next.config.ts ├── package.json ├── postcss.config.mjs ├── public │ ├── file.svg │ ├── globe.svg │ ├── logo.svg │ ├── next.svg │ ├── vercel.svg │ └── window.svg ├── src │ └── app │ │ ├── [[...mdxPath]] │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── logo.svg ├── tsconfig.json └── utils │ └── path.ts ├── infrastructure ├── Brewfile ├── maskfile.md ├── nginx │ ├── Dockerfile │ ├── README.md │ └── default.conf.template └── terraform │ ├── .gitignore │ ├── README.md │ ├── environments │ ├── blake │ │ ├── .terraform.lock.hcl │ │ └── main.tf │ ├── cloudflare │ │ ├── .terraform.lock.hcl │ │ ├── README.md │ │ ├── main.tf │ │ └── outputs.tf │ ├── global_aws │ │ ├── .terraform.lock.hcl │ │ ├── README.md │ │ ├── github_actions_iam.tf │ │ ├── main.tf │ │ └── outputs.tf │ └── stevie │ │ ├── .terraform.lock.hcl │ │ └── main.tf │ └── modules │ ├── container-generic │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ ├── core-services │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ ├── deployment │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ ├── ecr-repositories │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ ├── honeycomb-integration │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ └── v7-cluster │ ├── dns.tf │ ├── ecs.tf │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── jobs ├── .env.development ├── next.docker.config.js ├── package.json ├── src │ ├── clients.ts │ ├── defineJob.ts │ ├── index.ts │ ├── jobs │ │ └── emitEvent.ts │ ├── tracing.ts │ └── types.ts └── tsconfig.json ├── package.json ├── packages ├── context-editor │ ├── .d.ts │ ├── .env.development │ ├── .env.test │ ├── .gitignore │ ├── .storybook │ │ ├── main.ts │ │ └── preview.ts │ ├── README.md │ ├── package.json │ ├── playwright.config.ts │ ├── playwright │ │ ├── attribute-panel.spec.ts │ │ ├── blocks.spec.ts │ │ ├── constants.ts │ │ ├── marks.spec.ts │ │ ├── media.spec.ts │ │ ├── suggest.spec.ts │ │ ├── upload.spec.ts │ │ └── utils.ts │ ├── postcss.config.cjs │ ├── src │ │ ├── ContextEditor.tsx │ │ ├── commands │ │ │ ├── blocks.ts │ │ │ ├── figures.ts │ │ │ ├── horizontal.ts │ │ │ ├── images.ts │ │ │ ├── marks.ts │ │ │ ├── math.ts │ │ │ ├── tables.ts │ │ │ ├── types.ts │ │ │ └── util.ts │ │ ├── components │ │ │ ├── AtomDataMenu.tsx │ │ │ ├── AttributePanel.tsx │ │ │ ├── ImageUploader.tsx │ │ │ ├── MenuBar.tsx │ │ │ ├── StructureDecoration.tsx │ │ │ ├── SuggestPanel.tsx │ │ │ └── menus │ │ │ │ ├── AdvancedOptions.tsx │ │ │ │ ├── FigureMenu.tsx │ │ │ │ ├── LinkMenu.tsx │ │ │ │ ├── MarkMenu.tsx │ │ │ │ ├── MediaUpload.tsx │ │ │ │ ├── MenuFields.tsx │ │ │ │ └── NodeMenu.tsx │ │ ├── index.ts │ │ ├── plugins │ │ │ ├── code │ │ │ │ ├── codeMirrorBlockNodeView.ts │ │ │ │ ├── defaults.ts │ │ │ │ ├── index.ts │ │ │ │ ├── languageLoaders.ts │ │ │ │ ├── languages.ts │ │ │ │ ├── parsers.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── contextSuggest.ts │ │ │ ├── index.ts │ │ │ ├── inputRules.test.ts │ │ │ ├── inputRules.ts │ │ │ ├── keymap.ts │ │ │ ├── pasteRules.ts │ │ │ ├── reactProps.ts │ │ │ └── structureDecorations.tsx │ │ ├── schemas │ │ │ ├── blockquote.ts │ │ │ ├── code.ts │ │ │ ├── contextAtom.ts │ │ │ ├── contextDoc.ts │ │ │ ├── doc.ts │ │ │ ├── em.ts │ │ │ ├── figure.ts │ │ │ ├── hard_break.ts │ │ │ ├── heading.ts │ │ │ ├── horizontal.ts │ │ │ ├── image.ts │ │ │ ├── index.ts │ │ │ ├── link.ts │ │ │ ├── list.ts │ │ │ ├── math.ts │ │ │ ├── paragraph.ts │ │ │ ├── strike.ts │ │ │ ├── strong.ts │ │ │ ├── subSuperScript.ts │ │ │ ├── table.ts │ │ │ ├── text.ts │ │ │ └── underline.ts │ │ ├── stories │ │ │ ├── AtomRenderer.tsx │ │ │ ├── ContextDash.stories.tsx │ │ │ ├── ContextEditor.stories.tsx │ │ │ ├── EditorDash │ │ │ │ ├── EditorDash.tsx │ │ │ │ ├── JsonPanel.tsx │ │ │ │ ├── PubsPanel.tsx │ │ │ │ ├── SitePanel.tsx │ │ │ │ └── dashStyles.css │ │ │ ├── MediaUpload.stories.tsx │ │ │ ├── assets │ │ │ │ ├── data.csv │ │ │ │ ├── demo.mp4 │ │ │ │ ├── image0.jpg │ │ │ │ ├── image1.jpeg │ │ │ │ ├── image2.png │ │ │ │ ├── image3.jpg │ │ │ │ └── sounds.mp3 │ │ │ ├── doc.html │ │ │ ├── docWithImage.json │ │ │ ├── initialDoc.json │ │ │ ├── initialPubs.json │ │ │ ├── initialTypes.json │ │ │ └── mockUtils.ts │ │ ├── style.css │ │ ├── tailwind.css │ │ └── utils │ │ │ ├── emptyDoc.ts │ │ │ ├── getMarkRange.ts │ │ │ ├── hasChanged.ts │ │ │ ├── index.ts │ │ │ ├── links.ts │ │ │ ├── marks.ts │ │ │ ├── nodes.ts │ │ │ ├── pubValues.ts │ │ │ └── serialize.ts │ ├── tailwind.config.cjs │ ├── tsconfig.json │ ├── vite-env.d.ts │ └── vitest.config.mts ├── contracts │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── resources │ │ │ ├── internal.ts │ │ │ ├── site.ts │ │ │ └── types.ts │ └── tsconfig.json ├── db │ ├── .env.development │ ├── .kanelrc.cjs │ ├── package.json │ ├── scripts │ │ └── generateDbTableNames.ts │ ├── src │ │ ├── Database.ts │ │ ├── kanel │ │ │ ├── kanel-cleanup-enum-comments.cjs │ │ │ ├── kanel-database-default-export-fix-hook.cjs │ │ │ ├── kanel-history-table-generic.cjs │ │ │ └── kanel-kysely-zod-compatibility-hook.cjs │ │ ├── public.ts │ │ ├── public │ │ │ ├── Action.ts │ │ │ ├── ActionInstances.ts │ │ │ ├── ActionRunStatus.ts │ │ │ ├── ActionRuns.ts │ │ │ ├── ApiAccessLogs.ts │ │ │ ├── ApiAccessPermissions.ts │ │ │ ├── ApiAccessScope.ts │ │ │ ├── ApiAccessTokens.ts │ │ │ ├── ApiAccessType.ts │ │ │ ├── AuthTokenType.ts │ │ │ ├── AuthTokens.ts │ │ │ ├── BaseHistory.ts │ │ │ ├── Capabilities.ts │ │ │ ├── Communities.ts │ │ │ ├── CommunityMemberships.ts │ │ │ ├── CoreSchemaType.ts │ │ │ ├── CrudType.ts │ │ │ ├── ElementType.ts │ │ │ ├── Event.ts │ │ │ ├── FormAccessType.ts │ │ │ ├── FormElementToPubType.ts │ │ │ ├── FormElements.ts │ │ │ ├── Forms.ts │ │ │ ├── InputComponent.ts │ │ │ ├── InviteForms.ts │ │ │ ├── InviteStatus.ts │ │ │ ├── InviteToForms.ts │ │ │ ├── Invites.ts │ │ │ ├── InvitesHistory.ts │ │ │ ├── MemberGroupToUser.ts │ │ │ ├── MemberGroups.ts │ │ │ ├── MemberRole.ts │ │ │ ├── MembershipCapabilities.ts │ │ │ ├── MembershipType.ts │ │ │ ├── ModifiedByType.ts │ │ │ ├── MoveConstraint.ts │ │ │ ├── OperationType.ts │ │ │ ├── PrismaMigrations.ts │ │ │ ├── PubFieldSchema.ts │ │ │ ├── PubFieldToPubType.ts │ │ │ ├── PubFields.ts │ │ │ ├── PubMemberships.ts │ │ │ ├── PubTypes.ts │ │ │ ├── PubValues.ts │ │ │ ├── PubValuesHistory.ts │ │ │ ├── PublicSchema.ts │ │ │ ├── Pubs.ts │ │ │ ├── PubsInStages.ts │ │ │ ├── Rules.ts │ │ │ ├── Sessions.ts │ │ │ ├── StageMemberships.ts │ │ │ ├── Stages.ts │ │ │ ├── StructuralFormElement.ts │ │ │ └── Users.ts │ │ ├── table-names.ts │ │ └── types │ │ │ ├── ApiAccessToken.ts │ │ │ ├── HistoryTable.ts │ │ │ ├── Invite.ts │ │ │ ├── LastModifiedBy.ts │ │ │ └── index.ts │ └── tsconfig.json ├── emails │ ├── .env.development │ ├── emails │ │ └── static │ │ │ └── a.svg │ ├── eslint.config.mjs │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ ├── password-reset.tsx │ │ ├── request-link-to-form.tsx │ │ ├── signup-invite.tsx │ │ └── verify-email.tsx │ └── tsconfig.json ├── logger │ ├── package.json │ ├── src │ │ ├── base.ts │ │ └── index.ts │ └── tsconfig.json ├── schemas │ ├── package.json │ ├── src │ │ ├── CoreSchemaWithIcons.ts │ │ ├── errors.ts │ │ ├── formats.ts │ │ ├── index.ts │ │ ├── schemaComponents.ts │ │ ├── schemas.ts │ │ ├── types.ts │ │ ├── zodTypesToCoreSchemas.test.ts │ │ └── zodTypesToCoreSchemas.ts │ └── tsconfig.json ├── ui │ ├── README.md │ ├── components.json │ ├── eslint.config.mjs │ ├── hooks │ │ └── use-mobile.tsx │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ │ ├── accordion.tsx │ │ ├── actionInstances │ │ │ ├── ActionInstanceSelector.tsx │ │ │ ├── ActionInstanceSelectorInput.tsx │ │ │ ├── ActionInstancesContext.tsx │ │ │ └── index.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── auto-form │ │ │ ├── common │ │ │ │ ├── description.tsx │ │ │ │ ├── label.tsx │ │ │ │ └── tooltip.tsx │ │ │ ├── config.ts │ │ │ ├── dependencies.ts │ │ │ ├── dependencyType.ts │ │ │ ├── fields │ │ │ │ ├── array.tsx │ │ │ │ ├── checkbox.tsx │ │ │ │ ├── date.tsx │ │ │ │ ├── enum.tsx │ │ │ │ ├── file.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── number.tsx │ │ │ │ ├── object.tsx │ │ │ │ ├── radio-group.tsx │ │ │ │ ├── switch.tsx │ │ │ │ └── textarea.tsx │ │ │ ├── index.tsx │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── autocomplete.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── color.tsx │ │ ├── combobox.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── copy-button.tsx │ │ ├── customRenderers │ │ │ ├── confidence │ │ │ │ ├── confidence.css │ │ │ │ └── confidence.tsx │ │ │ └── fileUpload │ │ │ │ └── fileUpload.tsx │ │ ├── data-table-paged │ │ │ ├── components │ │ │ │ ├── data-table-pagination.tsx │ │ │ │ └── data-table.tsx │ │ │ ├── config │ │ │ │ └── data-table.ts │ │ │ ├── hooks │ │ │ │ ├── use-callback-ref.ts │ │ │ │ ├── use-data-table.ts │ │ │ │ └── use-debounced-callback.ts │ │ │ ├── index.tsx │ │ │ ├── lib │ │ │ │ ├── data-table.ts │ │ │ │ └── parsers.ts │ │ │ └── types │ │ │ │ └── index.ts │ │ ├── data-table.tsx │ │ ├── date-picker.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── editors │ │ │ ├── LexicalEditor.tsx │ │ │ ├── SingleLinePlugin.tsx │ │ │ ├── TokenNode.ts │ │ │ ├── TokenPlugin.tsx │ │ │ └── index.tsx │ │ ├── form.tsx │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useLocalStorage.tsx │ │ │ ├── useMobile.tsx │ │ │ └── useUnsavedChangesWarning.tsx │ │ ├── hover-card.tsx │ │ ├── icon.tsx │ │ ├── index.tsx │ │ ├── input.tsx │ │ ├── kbd.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── multi-select.tsx │ │ ├── multiblock.tsx │ │ ├── multivalue-input.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── pubFields │ │ │ ├── PubFieldContext.tsx │ │ │ ├── index.tsx │ │ │ └── pubFieldSelect │ │ │ │ ├── PubFieldSelect.tsx │ │ │ │ ├── determinePubFields.ts │ │ │ │ └── index.ts │ │ ├── pubTypes │ │ │ ├── PubTypesContext.tsx │ │ │ └── index.tsx │ │ ├── radio-group.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── show-more.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── toggle.tsx │ │ ├── tokens │ │ │ ├── TokenContext.tsx │ │ │ └── index.tsx │ │ ├── tooltip.tsx │ │ └── use-toast.tsx │ ├── styles.css │ ├── tailwind.config.cjs │ └── tsconfig.json └── utils │ ├── package.json │ ├── src │ ├── assert.ts │ ├── classnames.ts │ ├── color.ts │ ├── doi.ts │ ├── index.ts │ ├── sleep.ts │ ├── try-catch.ts │ ├── url.ts │ └── uuid.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── self-host ├── .env.example ├── .gitignore ├── README.md ├── caddy │ ├── Caddyfile │ └── Caddyfile.test ├── create-admin.sh ├── docker-compose.yml └── minio │ └── .gitkeep ├── storybook ├── .storybook │ ├── main.ts │ └── preview.ts ├── README.md ├── package.json ├── postcss.config.js ├── stories │ ├── Button.stories.tsx │ ├── MultiSelect.stories.tsx │ └── MultiValueInput.stories.tsx ├── tailwind.config.ts └── tsconfig.json └── turbo.json /.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/go/build-context-dockerignore/ 6 | 7 | **/.classpath 8 | **/.dockerignore 9 | **/.env 10 | **/.git 11 | **/.project 12 | **/.settings 13 | **/.toolstarget 14 | **/.vs 15 | **/.vscode 16 | **/.next 17 | **/.cache 18 | **/*.*proj.user 19 | **/*.dbmdl 20 | **/*.jfm 21 | **/charts 22 | **/docker-compose* 23 | **/compose* 24 | **/Dockerfile* 25 | **/node_modules 26 | **/npm-debug.log 27 | **/obj 28 | **/secrets.dev.yaml 29 | **/values.dev.yaml 30 | **/build 31 | **/dist 32 | LICENSE 33 | README.md 34 | -------------------------------------------------------------------------------- /.env.docker-compose.dev: -------------------------------------------------------------------------------- 1 | MINIO_ROOT_USER=pubpub-minio-admin 2 | MINIO_ROOT_PASSWORD=pubpub-minio-admin 3 | 4 | ASSETS_BUCKET_NAME=assets.v7.pubpub.org 5 | ASSETS_UPLOAD_KEY=pubpubuser 6 | ASSETS_UPLOAD_SECRET_KEY=pubpubpass 7 | ASSETS_REGION=us-east-1 8 | ASSETS_STORAGE_ENDPOINT=http://localhost:9000 9 | 10 | POSTGRES_PORT=54322 11 | POSTGRES_USER=postgres 12 | POSTGRES_PASSWORD=postgres 13 | POSTGRES_DB=postgres 14 | 15 | VALKEY_HOST='cache' -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Issue 3 | about: Suggest a new feature at a high level. 4 | title: '' 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Motivation 11 | 12 | 13 | ## Requirements 14 | 15 | 16 | ## Acceptance Criteria 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Issue(s) Resolved 2 | 3 | ## High-level Explanation of PR 4 | 5 | 6 | 7 | ## Test Plan 8 | 9 | ## Screenshots (if applicable) 10 | 11 | ## Notes 12 | -------------------------------------------------------------------------------- /.github/workflows/pull-preview-script.sh: -------------------------------------------------------------------------------- 1 | # install latest version of docker compose, by default it's using an ancient version 2 | sudo curl -sL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-"$(uname -m)" \ 3 | -o "$(which docker-compose)" && sudo chmod +x "$(which docker-compose)" 4 | 5 | docker image prune -a -f 6 | 7 | df -h 8 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm format 5 | pnpm lint 6 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm format 5 | pnpm lint 6 | pnpm type-check 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | enable-pre-post-scripts = true -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.13.1 -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-playwright.playwright", 4 | "YoavBls.pretty-ts-errors", 5 | "esbenp.prettier-vscode", 6 | "dbaeumer.vscode-eslint", 7 | // for yaml autocompletion using the # yaml-language-server: $schema=... directive 8 | "redhat.vscode-yaml" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Next.js: debug server-side", 6 | "type": "node-terminal", 7 | "request": "launch", 8 | "command": "pnpm run dev", 9 | "cwd": "${workspaceFolder}", 10 | "skipFiles": ["/**"] 11 | }, 12 | { 13 | "name": "Next.js: debug client-side", 14 | "type": "chrome", 15 | "request": "launch", 16 | "url": "http://localhost:3000" 17 | }, 18 | { 19 | "name": "Next.js: debug full stack", 20 | "type": "node-terminal", 21 | "request": "launch", 22 | "command": "pnpm run dev", 23 | "cwd": "${workspaceFolder}/core", 24 | "serverReadyAction": { 25 | "pattern": "- Local:.+(https?://.+)", 26 | "uriFormat": "%s", 27 | "action": "debugWithChrome" 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.css": "tailwindcss" 4 | }, 5 | "typescript.tsdk": "./core/node_modules/typescript/lib", 6 | "javascript.preferences.autoImportSpecifierExcludeRegexes": ["@prisma\\/client"], 7 | // these variables are necessary in order to correctly run seeds 8 | "playwright.env": { 9 | "NODE_OPTIONS": "--import=\"#register-loader\"", 10 | "SKIP_VALIDATION": "true", 11 | "DATABASE_URL": "postgresql://postgres:postgres@localhost:54322/postgres" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], 3 | }; 4 | -------------------------------------------------------------------------------- /bin/render-build.sh: -------------------------------------------------------------------------------- 1 | set -euxo pipefail 2 | 3 | npm i -g pnpm@8.7.0 4 | pnpm install 5 | pnpm p:build 6 | pnpm --filter "$1" build -------------------------------------------------------------------------------- /config/eslint/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import baseConfig from "./base.js"; 2 | 3 | export default [...baseConfig]; 4 | -------------------------------------------------------------------------------- /config/eslint/react.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import jsxA11yPlugin from "eslint-plugin-jsx-a11y"; 4 | import reactPlugin from "eslint-plugin-react"; 5 | import reactCompilerPlugin from "eslint-plugin-react-compiler"; 6 | import validateJsxNestingPlugin from "eslint-plugin-validate-jsx-nesting"; 7 | 8 | /** @type {Awaited} */ 9 | export default [ 10 | jsxA11yPlugin.flatConfigs.recommended, 11 | { 12 | plugins: { 13 | react: reactPlugin, 14 | "validate-jsx-nesting": validateJsxNestingPlugin, 15 | "react-compiler": reactCompilerPlugin, 16 | }, 17 | rules: { 18 | "react/jsx-key": "error", 19 | "validate-jsx-nesting/no-invalid-jsx-nesting": "error", 20 | "react-compiler/react-compiler": "error", 21 | }, 22 | settings: { 23 | react: { 24 | version: "detect", 25 | }, 26 | }, 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /config/eslint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", 6 | "typeRoots": ["node_modules/@types", "./types.d.ts"] 7 | }, 8 | "include": ["."], 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /config/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pubpub/prettier-config", 3 | "type": "module", 4 | "private": true, 5 | "version": "0.1.0", 6 | "exports": { 7 | ".": "./index.js" 8 | }, 9 | "scripts": { 10 | "clean": "rm -rf .turbo node_modules", 11 | "format": "prettier --check . --ignore-path ../../.gitignore", 12 | "format:fix": "prettier -w . --ignore-path ../../.gitignore", 13 | "type-check": "tsc --noEmit" 14 | }, 15 | "dependencies": { 16 | "@ianvs/prettier-plugin-sort-imports": "^4.1.1", 17 | "prettier": "catalog:", 18 | "prettier-plugin-jsdoc": "^1.3.0", 19 | "prettier-plugin-tailwindcss": "^0.6.6" 20 | }, 21 | "devDependencies": { 22 | "tsconfig": "workspace:*", 23 | "typescript": "catalog:" 24 | }, 25 | "prettier": "@pubpub/prettier-config" 26 | } 27 | -------------------------------------------------------------------------------- /config/prettier/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "noEmit": true, 6 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 7 | }, 8 | "include": ["./*.js"], 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /config/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "lib": ["es2019"], 7 | "target": "ESNext", 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "inlineSources": false, 11 | "isolatedModules": true, 12 | "moduleResolution": "node", 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "preserveWatchOutput": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "incremental": true, 19 | "allowJs": true, 20 | "noEmit": true, 21 | "verbatimModuleSyntax": true 22 | }, 23 | "exclude": ["node_modules", "build", "dist", ".next"] 24 | } 25 | -------------------------------------------------------------------------------- /config/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "allowImportingTsExtensions": true, 7 | "target": "es2018", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "useUnknownInCatchVariables": false, 14 | "incremental": true, 15 | "esModuleInterop": true, 16 | "module": "esnext", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "downlevelIteration": true, 21 | "moduleResolution": "Bundler" 22 | }, 23 | "include": ["src", "next-env.d.ts"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /config/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "type": "module", 4 | "version": "0.0.0", 5 | "private": true, 6 | "license": "MIT", 7 | "scripts": { 8 | "type-check": "echo 'Cant typecheck typescript silly'" 9 | }, 10 | "publishConfig": { 11 | "access": "public" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /config/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["DOM", "ESNext"], 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "jsx": "react-jsx", 10 | "moduleResolution": "bundler" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/.env.docker: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}:${PGPORT}/${PGDATABASE} 2 | -------------------------------------------------------------------------------- /core/.env.template: -------------------------------------------------------------------------------- 1 | OTEL_SERVICE_NAME="pubpub-v7-dev" # should be shared across components but not environments 2 | HONEYCOMB_API_KEY="" 3 | -------------------------------------------------------------------------------- /core/.env.test: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgresql://postgres:postgres@localhost:54323/postgres 2 | PUBPUB_URL=http://localhost:3000 3 | MAILGUN_SMTP_HOST=localhost 4 | MAILGUN_SMTP_PORT=54325 5 | API_KEY="super_secret_key" 6 | 7 | ASSETS_BUCKET_NAME=byron.v7.pubpub.org 8 | ASSETS_UPLOAD_KEY=pubpubuserrr 9 | ASSETS_UPLOAD_SECRET_KEY=pubpubpass 10 | ASSETS_REGION=us-east-1 11 | ASSETS_STORAGE_ENDPOINT="http://localhost:9000" 12 | 13 | MAILGUN_SMTP_PASSWORD="xxx" 14 | MAILGUN_SMTP_USERNAME="xxx" 15 | 16 | OTEL_SERVICE_NAME="pubpub-v7-dev" # should be shared across components but not environments 17 | HONEYCOMB_API_KEY="xxx" 18 | 19 | # KYSELY_DEBUG="true" 20 | 21 | GCLOUD_KEY_FILE='xxx' 22 | VALKEY_HOST='cache' -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /blob-report/ 5 | /playwright/.cache/ 6 | 7 | playwright/.auth 8 | -------------------------------------------------------------------------------- /core/.prettierignore: -------------------------------------------------------------------------------- 1 | app/c/\[communitySlug\]/developers/docs/stoplight.styles.css 2 | vitest-bench.local.json 3 | **/*.html -------------------------------------------------------------------------------- /core/actions/_lib/custom-form-field/getCustomConfigComponent.tsx: -------------------------------------------------------------------------------- 1 | import type { actions } from "../../api"; 2 | import type { ActionConfigServerComponent } from "./defineConfigServerComponent"; 3 | 4 | export const getCustomConfigComponentByActionName = async < 5 | A extends keyof typeof actions, 6 | T extends "config" | "params", 7 | C extends Extract, 8 | >( 9 | actionName: A, 10 | type: T, 11 | fieldName: C 12 | ) => { 13 | try { 14 | const action = await import(`../../${actionName}/${type}/${fieldName}.field.tsx`); 15 | return action.default as ActionConfigServerComponent<(typeof actions)[A], T>; 16 | } catch (error) { 17 | return null; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /core/actions/_lib/getRuns.ts: -------------------------------------------------------------------------------- 1 | import { logger } from "logger"; 2 | 3 | import type * as Runs from "../runs"; 4 | 5 | export const getActionRunByName = async (name: T) => { 6 | logger.info({ msg: `Loading action run for ${name}` }); 7 | try { 8 | return (await import(`../${name}/run`)).run as (typeof Runs)[T]; 9 | } catch (error) { 10 | logger.error({ msg: `Failed to load action run for ${name}`, error }); 11 | return null; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /core/actions/api/client.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export { resolveFieldConfig } from "../_lib/custom-form-field/resolveFieldConfig"; 4 | -------------------------------------------------------------------------------- /core/actions/api/server.ts: -------------------------------------------------------------------------------- 1 | export { scheduleActionInstances } from "../_lib/scheduleActionInstance"; 2 | export { runInstancesForEvent, runActionInstance } from "../_lib/runActionInstance"; 3 | export { resolveFieldConfig } from "../_lib/custom-form-field/resolveFieldConfig"; 4 | -------------------------------------------------------------------------------- /core/actions/googleDriveImport/config/outputField.field.tsx: -------------------------------------------------------------------------------- 1 | import { CoreSchemaType } from "db/public"; 2 | 3 | import { defineActionFormFieldServerComponent } from "../../_lib/custom-form-field/defineConfigServerComponent"; 4 | import { action } from "../action"; 5 | import OutputField from "../OutputField"; 6 | 7 | const component = defineActionFormFieldServerComponent( 8 | action, 9 | "config", 10 | async ({ action, actionInstance, stageId, communityId }) => { 11 | return ( 12 | 17 | ); 18 | } 19 | ); 20 | 21 | export default component; 22 | -------------------------------------------------------------------------------- /core/actions/googleDriveImport/params/outputField.field.tsx: -------------------------------------------------------------------------------- 1 | import { CoreSchemaType } from "db/public"; 2 | 3 | import { defineActionFormFieldServerComponent } from "../../_lib/custom-form-field/defineConfigServerComponent"; 4 | import { action } from "../action"; 5 | import OutputField from "../OutputField"; 6 | 7 | const component = defineActionFormFieldServerComponent( 8 | action, 9 | "config", 10 | async ({ action, actionInstance, stageId, communityId }) => { 11 | return ( 12 | 17 | ); 18 | } 19 | ); 20 | 21 | export default component; 22 | -------------------------------------------------------------------------------- /core/actions/http/config/outputMap.field.tsx: -------------------------------------------------------------------------------- 1 | import { defineActionFormFieldServerComponent } from "../../_lib/custom-form-field/defineConfigServerComponent"; 2 | import { action } from "../action"; 3 | import { FieldOutputMap } from "./client-components/FieldOutputMap"; 4 | 5 | const component = defineActionFormFieldServerComponent( 6 | action, 7 | "config", 8 | async ({ action, actionInstance, stageId, communityId }) => { 9 | return ; 10 | } 11 | ); 12 | 13 | export default component; 14 | -------------------------------------------------------------------------------- /core/actions/http/params/outputMap.field.tsx: -------------------------------------------------------------------------------- 1 | import { defineActionFormFieldServerComponent } from "../../_lib/custom-form-field/defineConfigServerComponent"; 2 | import { action } from "../action"; 3 | import { FieldOutputMap } from "../config/client-components/FieldOutputMap"; 4 | 5 | const component = defineActionFormFieldServerComponent( 6 | action, 7 | "params", 8 | async ({ action, actionInstance, stageId, communityId, pubId }) => { 9 | return ; 10 | } 11 | ); 12 | 13 | export default component; 14 | -------------------------------------------------------------------------------- /core/actions/log/action.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | import { Action } from "db/public"; 4 | import { Terminal } from "ui/icon"; 5 | 6 | import { defineAction } from "../types"; 7 | 8 | export const action = defineAction({ 9 | name: Action.log, 10 | config: { 11 | schema: z.object({ 12 | debounce: z.number().optional().describe("Debounce time in milliseconds."), 13 | }), 14 | }, 15 | description: "Log a pub to the console", 16 | params: { 17 | schema: z 18 | .object({ 19 | debounce: z.number().optional().describe("Debounce time in milliseconds."), 20 | text: z 21 | .string() 22 | .describe("The string to log out in addition to the default parameters") 23 | .optional(), 24 | }) 25 | .optional(), 26 | }, 27 | icon: Terminal, 28 | superAdminOnly: true, 29 | }); 30 | -------------------------------------------------------------------------------- /core/actions/log/run.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { logger } from "logger"; 4 | 5 | import type { action } from "./action"; 6 | import { defineRun } from "../types"; 7 | 8 | export const run = defineRun(async ({ actionInstance, pub, config, args }) => { 9 | logger.info({ 10 | msg: `Logging${args?.text ? ` ${args.text}` : ""}`, 11 | pub, 12 | config, 13 | args, 14 | }); 15 | 16 | return { 17 | success: true, 18 | report: `Logged out ${args?.text || "some data"}, check your console.`, 19 | title: `Successfully ran ${actionInstance.name}`, 20 | data: {}, 21 | }; 22 | }); 23 | -------------------------------------------------------------------------------- /core/actions/move/action.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | import { Action } from "db/public"; 4 | import { MoveHorizontal } from "ui/icon"; 5 | 6 | import { defineAction } from "../types"; 7 | 8 | export const action = defineAction({ 9 | name: Action.move, 10 | config: { 11 | schema: z.object({ 12 | stage: z.string().uuid().describe("Destination stage"), 13 | }), 14 | fieldConfig: { 15 | stage: { 16 | fieldType: "custom", 17 | }, 18 | }, 19 | }, 20 | description: "Move a pub to a different stage", 21 | params: { schema: z.object({}).optional() }, 22 | icon: MoveHorizontal, 23 | }); 24 | -------------------------------------------------------------------------------- /core/actions/move/config/stage.field.tsx: -------------------------------------------------------------------------------- 1 | import { defineActionFormFieldServerComponent } from "~/actions/_lib/custom-form-field/defineConfigServerComponent"; 2 | import { StageSelectServer } from "~/app/components/StageSelect/StageSelectServer"; 3 | import { action } from "../action"; 4 | 5 | const component = defineActionFormFieldServerComponent( 6 | action, 7 | "params", 8 | async ({ communityId, actionInstance }) => { 9 | return ( 10 | 15 | ); 16 | } 17 | ); 18 | 19 | export default component; 20 | -------------------------------------------------------------------------------- /core/actions/pdf/action.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | import { Action } from "db/public"; 4 | import { FileText } from "ui/icon"; 5 | 6 | import { defineAction } from "../types"; 7 | 8 | export const action = defineAction({ 9 | name: Action.pdf, 10 | config: { 11 | schema: z.object({ 12 | margin: z.number().optional().describe("Page margin in pixels"), 13 | }), 14 | }, 15 | description: "Generate a PDF from a pub", 16 | params: { 17 | schema: z 18 | .object({ 19 | margin: z.number().optional().describe("Page margin in pixels"), 20 | }) 21 | .optional(), 22 | }, 23 | icon: FileText, 24 | superAdminOnly: true, 25 | }); 26 | -------------------------------------------------------------------------------- /core/actions/pdf/run.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { logger } from "logger"; 4 | 5 | import type { action } from "./action"; 6 | import { defineRun } from "../types"; 7 | 8 | export const run = defineRun(async ({ pub, config, args }) => { 9 | await new Promise((resolve) => setTimeout(resolve, 1000)); 10 | logger.info({ msg: "pdf generated", pub, config, args }); 11 | return { 12 | error: "Wow, an error", 13 | }; 14 | }); 15 | -------------------------------------------------------------------------------- /core/actions/pushToV6/action.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | import { Action } from "db/public"; 4 | import { FileText } from "ui/icon"; 5 | 6 | import { defineAction } from "../types"; 7 | 8 | export const action = defineAction({ 9 | name: Action.pushToV6, 10 | config: { 11 | schema: z.object({ 12 | communitySlug: z.string().describe("Community slug"), 13 | authToken: z.string().describe("PubPub v6 API auth token"), 14 | title: z.string().describe("Title of the Pub"), 15 | content: z.string().describe("Content of the Pub"), 16 | idField: z 17 | .string() 18 | .regex(/\w+:\w+/) 19 | .describe("Field on this pub to write to id to|ID Field"), 20 | }), 21 | }, 22 | description: "Sync a PubPub Platform pub to v6", 23 | params: { schema: z.object({}).optional() }, 24 | icon: FileText, 25 | superAdminOnly: true, 26 | }); 27 | -------------------------------------------------------------------------------- /core/actions/runs.ts: -------------------------------------------------------------------------------- 1 | export { run as pdf } from "./pdf/run"; 2 | export { run as email } from "./email/run"; 3 | export { run as log } from "./log/run"; 4 | export { run as move } from "./move/run"; 5 | export { run as pushToV6 } from "./pushToV6/run"; 6 | export { run as http } from "./http/run"; 7 | export { run as googleDriveImport } from "./googleDriveImport/run"; 8 | export { run as datacite } from "./datacite/run"; 9 | -------------------------------------------------------------------------------- /core/app/(user)/UserStyles.module.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | width: 25px; 3 | height: 25px; 4 | } 5 | -------------------------------------------------------------------------------- /core/app/(user)/communities/CommunityTable.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | 5 | import type { TableCommunity } from "./getCommunityTableColumns"; 6 | import { DataTable } from "~/app/components/DataTable/DataTable"; 7 | import { getCommunityTableColumns } from "./getCommunityTableColumns"; 8 | 9 | export const CommunityTable = ({ communities }: { communities: TableCommunity[] }) => { 10 | const communityTableColumns = getCommunityTableColumns(); 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /core/app/(user)/confirm/page.tsx: -------------------------------------------------------------------------------- 1 | export default async function Page() { 2 | return ( 3 | <> 4 |

Confirming...

5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /core/app/(user)/forgot/page.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "ui/card"; 2 | 3 | import ForgotForm from "./ForgotForm"; 4 | 5 | export default async function Page() { 6 | return ( 7 |
8 | 9 | 10 | Forgot password 11 | 12 | Enter your account's email address below to receive a secure link for 13 | resetting your password. 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /core/app/(user)/layout.tsx: -------------------------------------------------------------------------------- 1 | export default async function UserLayout({ children }: { children: React.ReactNode }) { 2 | return ( 3 |
4 |
5 | 6 |
7 |
{children}
8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /core/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/app/apple-icon.png -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/activity/actions/ActionRunsTable.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type { ActionRun } from "./getActionRunsTableColumns"; 4 | import { DataTable } from "~/app/components/DataTable/DataTable"; 5 | import { getActionRunsTableColumns } from "./getActionRunsTableColumns"; 6 | 7 | export const ActionRunsTable = ({ 8 | actionRuns, 9 | communitySlug, 10 | }: { 11 | actionRuns: ActionRun[]; 12 | communitySlug: string; 13 | }) => { 14 | const actionRunsColumns = getActionRunsTableColumns(communitySlug); 15 | return ; 16 | }; 17 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/developers/docs/openapi.json/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | import { createOpenApiDocument } from "./openApi"; 4 | 5 | export const dynamic = "force-dynamic"; 6 | 7 | export const GET = async function ( 8 | request: NextRequest, 9 | props: { params: Promise<{ communitySlug: string }> } 10 | ) { 11 | const params = await props.params; 12 | return NextResponse.json(createOpenApiDocument(params.communitySlug)); 13 | }; 14 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/forms/[formSlug]/edit/FormCopyButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import dynamic from "next/dynamic"; 4 | 5 | import { CopyButton } from "ui/copy-button"; 6 | 7 | import { useCommunity } from "~/app/components/providers/CommunityProvider"; 8 | 9 | const FormCopyButtonBase = ({ formSlug }: { formSlug: string }) => { 10 | const community = useCommunity(); 11 | const link = `${window.location.origin}/c/${community.slug}/public/forms/${formSlug}/fill`; 12 | return ( 13 | 14 | Copy link to live form 15 | 16 | ); 17 | }; 18 | 19 | // necessary in order to disable SSR, as window is not available on the server 20 | export const FormCopyButton = dynamic(() => Promise.resolve(FormCopyButtonBase), { ssr: false }); 21 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/members/MemberTable.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | import type { TableMember } from "./getMemberTableColumns"; 6 | import { DataTable } from "~/app/components/DataTable/DataTable"; 7 | import { getMemberTableColumns } from "./getMemberTableColumns"; 8 | 9 | export const MemberTable = ({ members }: { members: TableMember[] }) => { 10 | const memberTableColumns = getMemberTableColumns(); 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { getPageLoginData } from "~/lib/authentication/loginData"; 2 | 3 | export default async function Page() { 4 | await getPageLoginData(); 5 | return ( 6 | <> 7 |

Dashboard

8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/search/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return ( 3 | <> 4 |

Search

5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/settings/tokens/CreateTokenFormContext.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createContext } from "react"; 4 | 5 | import type { CreateTokenFormContext as CreateTokenFormContextType } from "db/types"; 6 | import { NO_STAGE_OPTION } from "db/types"; 7 | 8 | export const CreateTokenFormContext = createContext({ 9 | stages: { 10 | stages: [], 11 | allOptions: [NO_STAGE_OPTION], 12 | allValues: [NO_STAGE_OPTION.value], 13 | }, 14 | pubTypes: { 15 | pubTypes: [], 16 | allOptions: [], 17 | allValues: [], 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/types/TypeList.tsx: -------------------------------------------------------------------------------- 1 | import type { PubTypeWithFieldIds } from "~/lib/types"; 2 | import TypeBlock from "./TypeBlock"; 3 | 4 | type Props = { 5 | types: PubTypeWithFieldIds[]; 6 | allowEditing: boolean; 7 | }; 8 | 9 | const TypeList: React.FC = function ({ types, allowEditing }) { 10 | return ( 11 |
12 | {types.map((type) => { 13 | return ( 14 |
15 | 16 |
17 | ); 18 | })} 19 |
20 | ); 21 | }; 22 | export default TypeList; 23 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/types/utils.ts: -------------------------------------------------------------------------------- 1 | import { CoreSchemaType } from "db/public"; 2 | 3 | export const pubFieldCanBeTitle = (pubField: { 4 | schemaName: CoreSchemaType | null; 5 | isRelation?: boolean | null; 6 | }) => { 7 | return ( 8 | !pubField.isRelation && 9 | (pubField.schemaName === CoreSchemaType.String || 10 | pubField.schemaName === CoreSchemaType.Email || 11 | pubField.schemaName === CoreSchemaType.URL) 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /core/app/c/[communitySlug]/unauthorized/page.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | params: Promise<{ 3 | communitySlug: string; 4 | }>; 5 | }; 6 | 7 | export default async function Page(props: Props) { 8 | return ( 9 |
10 |
11 |

Unauthorized

12 |

13 | You are not authorized to view this page. 14 |

15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /core/app/components/ActionUI/PubsRunActionDropDownMenuItem.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { DropdownMenuItem } from "ui/dropdown-menu"; 4 | 5 | export const PubsRunActionDropDownMenuItem = (props: Parameters[0]) => { 6 | return ( 7 | { 10 | // prevents the dropdown from closing when clicking on the action 11 | evt.preventDefault(); 12 | }} 13 | > 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /core/app/components/ActiveArchiveTabs.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "ui/tabs"; 2 | 3 | export const ActiveArchiveTabs = ({ 4 | activeContent, 5 | archiveContent, 6 | className, 7 | }: { 8 | activeContent: React.ReactNode; 9 | archiveContent: React.ReactNode; 10 | className?: string; 11 | }) => { 12 | return ( 13 | 14 | 15 | Active 16 | Archived 17 | 18 |
19 | {activeContent} 20 | {archiveContent} 21 |
22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /core/app/components/ContextEditor/AtomRenderer.tsx: -------------------------------------------------------------------------------- 1 | import type { NodeViewComponentProps } from "@handlewithcare/react-prosemirror"; 2 | 3 | import { forwardRef } from "react"; 4 | import { useIsNodeSelected } from "@handlewithcare/react-prosemirror"; 5 | 6 | import { cn } from "utils"; 7 | 8 | export const ContextAtom = forwardRef(function ContextAtom( 9 | { nodeProps, ...props }, 10 | ref 11 | ) { 12 | const selected = useIsNodeSelected(); 13 | const activeNode = nodeProps.node; 14 | if (!activeNode) { 15 | return null; 16 | } 17 | return ( 18 |
24 |
{JSON.stringify(activeNode, null, 2)}
25 |
26 | ); 27 | }); 28 | -------------------------------------------------------------------------------- /core/app/components/CopyCurrentUrlButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import dynamic from "next/dynamic"; 4 | 5 | import type { CopyButtonProps } from "ui/copy-button"; 6 | import { CopyButton } from "ui/copy-button"; 7 | 8 | type Props = Omit; 9 | 10 | const CopyCurrentUrlButtonBase = (props: Props) => { 11 | return ; 12 | }; 13 | 14 | // necessary in order to disable SSR, as window is not available on the server 15 | export const CopyCurrentUrlButton = dynamic(() => Promise.resolve(CopyCurrentUrlButtonBase), { 16 | ssr: false, 17 | }); 18 | -------------------------------------------------------------------------------- /core/app/components/DataTable/v2/DataTable.tsx: -------------------------------------------------------------------------------- 1 | import type { DataTableProps } from "../DataTable"; 2 | import { DataTable as DataTableV1 } from "../DataTable"; 3 | 4 | /** 5 | * Wrapper around DataTable so that some fields can use updated designs 6 | */ 7 | export function DataTable( 8 | props: Pick< 9 | DataTableProps, 10 | | "columns" 11 | | "data" 12 | | "onRowClick" 13 | | "selectedRows" 14 | | "setSelectedRows" 15 | | "getRowId" 16 | | "defaultSort" 17 | > 18 | ) { 19 | return ( 20 | } 24 | hidePaginationWhenSinglePage 25 | className="border-none" 26 | striped 27 | /> 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /core/app/components/FormBuilder/ElementPanel/ComponentConfig/SelectDropdown.tsx: -------------------------------------------------------------------------------- 1 | import { InputComponent } from "db/public"; 2 | 3 | import type { ComponentConfigFormProps } from "./types"; 4 | import MultivalueBase from "./MultivalueBase"; 5 | 6 | export default (props: ComponentConfigFormProps) => { 7 | return ; 8 | }; 9 | -------------------------------------------------------------------------------- /core/app/components/FormBuilder/ElementPanel/ComponentConfig/types.ts: -------------------------------------------------------------------------------- 1 | import type { Static } from "@sinclair/typebox"; 2 | import type { UseFormReturn } from "react-hook-form"; 3 | import type { componentConfigSchemas } from "schemas"; 4 | 5 | import type { CoreSchemaType, InputComponent, PubTypesId } from "db/public"; 6 | 7 | export type ComponentConfigFormProps = { 8 | form: UseFormReturn>; 9 | schemaName: CoreSchemaType; 10 | component: I; 11 | }; 12 | 13 | export type ConfigFormData = { 14 | required: boolean | null; 15 | component: I; 16 | config: Static<(typeof componentConfigSchemas)[I]>; 17 | relatedPubTypes?: PubTypesId[]; 18 | }; 19 | 20 | export type FormType = UseFormReturn>; 21 | -------------------------------------------------------------------------------- /core/app/components/FormBuilder/FieldIcon.tsx: -------------------------------------------------------------------------------- 1 | import { SCHEMA_TYPES_WITH_ICONS } from "schemas"; 2 | 3 | import type { PubFields } from "db/public"; 4 | import type { LucideIcon } from "ui/icon"; 5 | import { BookDashed, Type } from "ui/icon"; 6 | 7 | export const FieldIcon = ({ 8 | field, 9 | className, 10 | }: { 11 | field: Pick; 12 | className?: string; 13 | }) => { 14 | let Icon: LucideIcon; 15 | if (!field.schemaName) { 16 | Icon = Type; 17 | } else if (field.isRelation) { 18 | Icon = BookDashed; 19 | } else { 20 | Icon = SCHEMA_TYPES_WITH_ICONS[field.schemaName].icon; 21 | } 22 | 23 | return ; 24 | }; 25 | -------------------------------------------------------------------------------- /core/app/components/FormBuilder/FormName.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/app/components/FormBuilder/FormName.tsx -------------------------------------------------------------------------------- /core/app/components/FormBuilder/FormPreview.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/app/components/FormBuilder/FormPreview.tsx -------------------------------------------------------------------------------- /core/app/components/FormBuilder/SaveFormButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "ui/button"; 4 | import { cn } from "utils"; 5 | 6 | import { useIsChanged } from "./useIsChanged"; 7 | 8 | type Props = { 9 | form: string; 10 | className?: string; 11 | disabled?: boolean; 12 | }; 13 | 14 | export const SaveFormButton = ({ form, className, disabled }: Props) => { 15 | const [isChanged] = useIsChanged(); 16 | return ( 17 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /core/app/components/FormBuilder/useIsChanged.tsx: -------------------------------------------------------------------------------- 1 | import { parseAsBoolean, useQueryState } from "nuqs"; 2 | 3 | export const useIsChanged = () => { 4 | const [isChanged, setIsChanged] = useQueryState( 5 | "unsavedChanges", 6 | parseAsBoolean.withDefault(false).withOptions({ 7 | history: "replace", 8 | scroll: false, 9 | }) 10 | ); 11 | 12 | return [isChanged, setIsChanged] as const; 13 | }; 14 | -------------------------------------------------------------------------------- /core/app/components/LastVisitedCommunity/SetLastVisited.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | 5 | import { LAST_VISITED_COOKIE, LAST_VISITED_COOKIE_MAX_AGE } from "./constants"; 6 | 7 | export default function SetLastVisited({ communitySlug }: { communitySlug: string }) { 8 | useEffect(() => { 9 | document.cookie = `${LAST_VISITED_COOKIE}=${communitySlug}; path=/; max-age=${LAST_VISITED_COOKIE_MAX_AGE}`; 10 | }, [communitySlug]); 11 | 12 | return <>; 13 | } 14 | -------------------------------------------------------------------------------- /core/app/components/LastVisitedCommunity/constants.ts: -------------------------------------------------------------------------------- 1 | export const LAST_VISITED_COOKIE = "lastVisitedCommunitySlug"; 2 | export const LAST_VISITED_COOKIE_MAX_AGE = 60 * 60 * 24 * 30; 3 | -------------------------------------------------------------------------------- /core/app/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react"; 2 | 3 | import { cn } from "utils"; 4 | 5 | const Logo = ({ className, ...props }: SVGProps) => { 6 | return ( 7 | 14 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | export default Logo; 31 | -------------------------------------------------------------------------------- /core/app/components/MemberSelect/types.ts: -------------------------------------------------------------------------------- 1 | import type { CommunityMemberships, Users } from "db/public"; 2 | 3 | export type MemberSelectUserWithMembership = Omit & { 4 | member: Omit; 5 | }; 6 | 7 | export type MemberSelectUser = Omit & { 8 | member?: Omit | null; 9 | }; 10 | 11 | export const isMemberSelectUserWithMembership = ( 12 | user: MemberSelectUser 13 | ): user is MemberSelectUserWithMembership => Boolean(user.member); 14 | -------------------------------------------------------------------------------- /core/app/components/Memberships/memberInviteFormSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | import { formsIdSchema, MemberRole } from "db/public"; 4 | 5 | export const memberInviteFormSchema = z.object({ 6 | email: z.string().email({ 7 | message: "Please provide a valid email address", 8 | }), 9 | role: z.nativeEnum(MemberRole).default(MemberRole.editor), 10 | firstName: z.string().optional(), 11 | lastName: z.string().optional(), 12 | isSuperAdmin: z.boolean().default(false).optional(), 13 | forms: z.array(formsIdSchema).default([]), 14 | }); 15 | -------------------------------------------------------------------------------- /core/app/components/Notice.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertDescription, AlertTitle } from "ui/alert"; 2 | import { AlertCircle } from "ui/icon"; 3 | import { cn } from "utils"; 4 | 5 | export type NoticeParams = { 6 | type: "error" | "notice"; 7 | title: string; 8 | body?: string; 9 | }; 10 | 11 | export const Notice = ({ 12 | type, 13 | title, 14 | body, 15 | className, 16 | }: { 17 | className?: string; 18 | } & NoticeParams) => ( 19 | svg]:static", className)} 22 | > 23 | 24 | {title} 25 | {body && {body}} 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /core/app/components/PubTitle.tsx: -------------------------------------------------------------------------------- 1 | import type { PubTitleProps } from "~/lib/pubs"; 2 | import { getPubTitle } from "~/lib/pubs"; 3 | 4 | export const PubTitle = (props: { pub: PubTitleProps }) => { 5 | const title = getPubTitle(props.pub); 6 | return

{title}

; 7 | }; 8 | -------------------------------------------------------------------------------- /core/app/components/Signup/schema.ts: -------------------------------------------------------------------------------- 1 | import type { Static } from "@sinclair/typebox"; 2 | 3 | import { Type } from "@sinclair/typebox"; 4 | import { TypeCompiler } from "@sinclair/typebox/compiler"; 5 | import { registerFormats } from "schemas"; 6 | 7 | registerFormats(); 8 | 9 | const signupFormSchema = Type.Object({ 10 | firstName: Type.String(), 11 | lastName: Type.String(), 12 | email: Type.String({ format: "email" }), 13 | password: Type.String({ 14 | minLength: 8, 15 | maxLength: 72, 16 | }), 17 | }); 18 | 19 | export const compiledSignupFormSchema = TypeCompiler.Compile(signupFormSchema); 20 | 21 | export type SignupFormSchema = Static; 22 | -------------------------------------------------------------------------------- /core/app/components/StageSelect/StageSelectServer.tsx: -------------------------------------------------------------------------------- 1 | import type { Communities, CommunitiesId, Stages, StagesId } from "db/public"; 2 | 3 | import { db } from "~/kysely/database"; 4 | import { autoCache } from "~/lib/server/cache/autoCache"; 5 | import { StageSelectClient } from "./StageSelectClient"; 6 | 7 | type Props = { 8 | communityId: CommunitiesId; 9 | fieldLabel: string; 10 | fieldName: string; 11 | }; 12 | 13 | export async function StageSelectServer({ communityId, fieldLabel, fieldName }: Props) { 14 | const stages = await autoCache( 15 | db.selectFrom("stages").selectAll().where("communityId", "=", communityId) 16 | ).execute(); 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /core/app/components/UserAvatar.tsx: -------------------------------------------------------------------------------- 1 | import type { Users } from "db/public"; 2 | import { Avatar, AvatarFallback, AvatarImage } from "ui/avatar"; 3 | 4 | type Props = { 5 | user: Pick; 6 | }; 7 | 8 | export const UserAvatar = ({ user }: Props) => { 9 | return ( 10 | 11 | 15 | 16 | {user.firstName[0]} 17 | {user.lastName?.[0] ?? ""} 18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /core/app/components/UserCard.tsx: -------------------------------------------------------------------------------- 1 | import type { Users } from "db/public"; 2 | 3 | import { UserAvatar } from "./UserAvatar"; 4 | 5 | type Props = { 6 | user: Pick; 7 | }; 8 | 9 | export const UserCard = (props: Props) => { 10 | return ( 11 |
12 | 13 |
14 |

15 | {props.user.firstName} {props.user.lastName} 16 |

17 |

{props.user.email}

18 |
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /core/app/components/providers/CommunityProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createContext, useContext } from "react"; 4 | 5 | import type { CommunityData } from "~/lib/server/community"; 6 | 7 | type Props = { 8 | children: React.ReactNode; 9 | community: CommunityData; 10 | }; 11 | 12 | const CommunityContext = createContext(undefined); 13 | 14 | export function CommunityProvider({ children, community }: Props) { 15 | return {children}; 16 | } 17 | 18 | export const useCommunity = () => { 19 | const community = useContext(CommunityContext); 20 | if (!community) { 21 | throw new Error("Community context used without provider"); 22 | } 23 | return community; 24 | }; 25 | -------------------------------------------------------------------------------- /core/app/components/providers/QueryProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 5 | 6 | import { client } from "~/lib/api"; 7 | 8 | export function ReactQueryProvider({ children }: React.PropsWithChildren) { 9 | const queryClient = new QueryClient(); 10 | 11 | return ( 12 | 13 | {children} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /core/app/components/pubs/PubEditor/PageTitleWithStatus.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useSaveStatus } from "~/app/components/pubs/PubEditor/SaveStatus"; 4 | 5 | export const PageTitleWithStatus = ({ title }: { title: string }) => { 6 | const status = useSaveStatus({ defaultMessage: "Form will save when you click save" }); 7 | return ( 8 |
9 | {title} 10 | 11 | {status} 12 | 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /core/app/components/pubs/PubEditor/constants.ts: -------------------------------------------------------------------------------- 1 | export const SAVE_STATUS_QUERY_PARAM = "saveStatus" as const; 2 | export const SUBMIT_ID_QUERY_PARAM = "submitId" as const; 3 | export const RELATED_PUB_SLUG = "relatedPubValue" as const; 4 | -------------------------------------------------------------------------------- /core/app/components/pubs/RemovePubForm.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import dynamic from "next/dynamic"; 3 | 4 | import type { PubRemoveProps } from "./RemovePubFormClient"; 5 | import { SkeletonCard } from "../skeletons/SkeletonCard"; 6 | 7 | const PubRemoveForm = dynamic( 8 | async () => { 9 | return import("./RemovePubFormClient").then((mod) => ({ 10 | default: mod.PubRemoveForm, 11 | })); 12 | }, 13 | { loading: () => } 14 | ); 15 | 16 | export async function PubRemove({ pubId, redirectTo }: PubRemoveProps) { 17 | return ( 18 | Loading...}> 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /core/app/components/skeletons/SkeletonButton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Skeleton } from "ui/skeleton"; 4 | import { cn } from "utils"; 5 | 6 | export const SkeletonButton = ({ className, ...props }: React.HTMLAttributes) => ( 7 | 8 | ); 9 | -------------------------------------------------------------------------------- /core/app/components/skeletons/SkeletonCard.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "ui/skeleton"; 2 | 3 | export const SkeletonCard = () => { 4 | return ( 5 |
6 | 7 |
8 | 9 | 10 |
11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /core/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/app/favicon.ico -------------------------------------------------------------------------------- /core/app/global-error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import NextError from "next/error"; 5 | import * as Sentry from "@sentry/nextjs"; 6 | 7 | export default function GlobalError({ error }: { error: Error & { digest?: string } }) { 8 | useEffect(() => { 9 | Sentry.captureException(error); 10 | }, [error]); 11 | 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /core/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Not clear why we need this here if we 6 | also import ui/styles which includes it */ 7 | -------------------------------------------------------------------------------- /core/app/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/app/pubs/[pubId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { notFound, redirect } from "next/navigation"; 2 | 3 | import type { PubsId } from "db/public"; 4 | 5 | import { getPlainPub } from "~/lib/server"; 6 | import { findCommunityByPubId } from "~/lib/server/community"; 7 | 8 | export type Props = { 9 | params: Promise<{ 10 | pubId: PubsId; 11 | }>; 12 | }; 13 | 14 | export default async function Page(props: Props) { 15 | const [pub, community] = await Promise.all([ 16 | getPlainPub((await props.params).pubId).executeTakeFirstOrThrow(), 17 | findCommunityByPubId((await props.params).pubId), 18 | ]); 19 | 20 | if (!pub || !community) { 21 | notFound(); 22 | } 23 | 24 | redirect(`/c/${community.slug}/pubs/${pub.id}`); 25 | } 26 | -------------------------------------------------------------------------------- /core/app/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /core/docs/pub-relationships.md: -------------------------------------------------------------------------------- 1 | # Pub Relationships 2 | 3 | In v6, we had Collections and Pubs, in addition to Connections. These allowed us to have parent-child relationships (Collections-Pubs) and arbitrary non-hierarchical, non-permission-granting relationships (Connections). The questions for v7 are: 4 | 5 | - Given Pub Types, can we reconcile these two in relationships into a more simple UI/UX 6 | - Do we need both relationship types? 7 | 8 | ## Permission cascading 9 | 10 | ## Performance considerations 11 | -------------------------------------------------------------------------------- /core/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import baseConfig, { restrictEnvAccess } from "@pubpub/eslint-config/base"; 4 | import nextConfig from "@pubpub/eslint-config/next"; 5 | import reactConfig from "@pubpub/eslint-config/react"; 6 | 7 | /** @type {import('typescript-eslint').Config} */ 8 | export default [ 9 | { 10 | ignores: [ 11 | ".next/**", 12 | "playwright-report/**", 13 | "playwright/.auth", 14 | "test-results/**", 15 | "**/*stub*.js", 16 | ], 17 | }, 18 | ...baseConfig, 19 | ...reactConfig, 20 | ...nextConfig, 21 | ...restrictEnvAccess, 22 | ]; 23 | -------------------------------------------------------------------------------- /core/instrumentation.edge.mts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | import { logger } from "logger"; 4 | 5 | import { env } from "./lib/env/env"; 6 | 7 | logger.info("Running instrumentation hook for edge..."); 8 | 9 | if (env.NODE_ENV === "production") { 10 | logger.info("Instrumenting Sentry..."); 11 | Sentry.init({ 12 | dsn: "https://5012643b47ea6b2c8917f14442066f23@o31718.ingest.sentry.io/4505959187480576", 13 | 14 | // Adjust this value in production, or use tracesSampler for greater control 15 | tracesSampleRate: 1, 16 | 17 | // Setting this option to true will print useful information to the console while you're setting up Sentry. 18 | debug: false, 19 | }); 20 | logger.info("✅ Successfully instrumented Sentry"); 21 | } 22 | 23 | logger.info("instrumentation hooked in for edge"); 24 | -------------------------------------------------------------------------------- /core/instrumentation.onRequestError.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | export const onRequestError = Sentry.captureRequestError; 4 | -------------------------------------------------------------------------------- /core/kysely/artificial-latency-plugin.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | KyselyPlugin, 3 | PluginTransformQueryArgs, 4 | PluginTransformResultArgs, 5 | QueryResult, 6 | RootOperationNode, 7 | UnknownRow, 8 | } from "kysely"; 9 | 10 | import { sleep } from "utils"; 11 | 12 | /** 13 | * Plugin which add artificial latency to all queries to more easily test Suspense etc 14 | */ 15 | export class ArtificialLatencyPlugin implements KyselyPlugin { 16 | constructor( 17 | /** 18 | * The delay in milliseconds 19 | */ 20 | public delay: number 21 | ) {} 22 | 23 | transformQuery(args: PluginTransformQueryArgs): RootOperationNode { 24 | return args.node; 25 | } 26 | 27 | async transformResult(args: PluginTransformResultArgs): Promise> { 28 | await sleep(this.delay); 29 | return args.result; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/kysely/database.ts: -------------------------------------------------------------------------------- 1 | import { env } from "~/lib/env/env"; 2 | import { createDatabase } from "./database-init"; 3 | 4 | export const { db, pool } = createDatabase({ 5 | url: env.DATABASE_URL, 6 | logLevel: env.LOG_LEVEL, 7 | debug: env.KYSELY_DEBUG === "true", 8 | nodeEnv: env.NODE_ENV, 9 | latency: env.KYSELY_ARTIFICIAL_LATENCY, 10 | }); 11 | -------------------------------------------------------------------------------- /core/lib/__tests__/fixtures/big-croc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/lib/__tests__/fixtures/big-croc.jpg -------------------------------------------------------------------------------- /core/lib/api.ts: -------------------------------------------------------------------------------- 1 | import { initTsrReactQuery } from "@ts-rest/react-query/v5"; 2 | 3 | import { siteApi } from "contracts"; 4 | 5 | import { env } from "./env/env"; 6 | 7 | export const client = initTsrReactQuery(siteApi, { 8 | baseUrl: typeof window === "undefined" ? env.PUBPUB_URL : window.location.origin, 9 | }); 10 | -------------------------------------------------------------------------------- /core/lib/authentication/api.ts: -------------------------------------------------------------------------------- 1 | import crypto from "node:crypto"; 2 | 3 | import { BadRequestError, UnauthorizedError } from "../server/errors"; 4 | 5 | export const getBearerToken = (authHeader: string) => { 6 | const parts = authHeader.split("Bearer "); 7 | if (parts.length !== 2) { 8 | throw new BadRequestError("Unable to parse authorization header"); 9 | } 10 | return parts[1]; 11 | }; 12 | 13 | export const compareAPIKeys = (key1: string, key2: string) => { 14 | if ( 15 | key1.length === key2.length && 16 | crypto.timingSafeEqual(new Uint8Array(Buffer.from(key1)), new Uint8Array(Buffer.from(key2))) 17 | ) { 18 | return; 19 | } 20 | 21 | throw new UnauthorizedError("Invalid API key"); 22 | }; 23 | -------------------------------------------------------------------------------- /core/lib/authentication/cookies.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from "next/server"; 2 | 3 | export const TOKEN_NAME = "token"; 4 | export const REFRESH_NAME = "refresh"; 5 | 6 | export function getTokenCookie(req: NextRequest) { 7 | return req.cookies.get(TOKEN_NAME)?.value; 8 | } 9 | 10 | export function getRefreshCookie(req: NextRequest) { 11 | return req.cookies.get(REFRESH_NAME)?.value; 12 | } 13 | -------------------------------------------------------------------------------- /core/lib/authentication/password.ts: -------------------------------------------------------------------------------- 1 | import { hash, verify } from "@node-rs/argon2"; 2 | 3 | /** 4 | * Validates a password against a user's password hash 5 | * 6 | */ 7 | export const validatePassword = async (password: string, passwordHash: string) => { 8 | const validPassword = await verify(passwordHash, password, { 9 | memoryCost: 19456, 10 | timeCost: 2, 11 | outputLen: 32, 12 | parallelism: 1, 13 | }); 14 | return validPassword; 15 | }; 16 | 17 | export const createPasswordHash = async (password: string) => { 18 | const passwordHash = await hash(password, { 19 | memoryCost: 19456, 20 | timeCost: 2, 21 | outputLen: 32, 22 | parallelism: 1, 23 | }); 24 | 25 | return passwordHash; 26 | }; 27 | -------------------------------------------------------------------------------- /core/lib/form.ts: -------------------------------------------------------------------------------- 1 | import { slugifyString } from "./string"; 2 | 3 | export const defaultFormName = (pubTypeName: string) => `${pubTypeName} Editor (Default)`; 4 | export const defaultFormSlug = (pubTypeName: string) => 5 | `${slugifyString(pubTypeName)}-default-editor`; 6 | -------------------------------------------------------------------------------- /core/lib/logging.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/nextjs"; 2 | 3 | import { logger } from "logger"; 4 | 5 | export function logError(message: string, error: Error) { 6 | logger.error({ message, error }); 7 | Sentry.captureException(error); 8 | } 9 | -------------------------------------------------------------------------------- /core/lib/redirect.ts: -------------------------------------------------------------------------------- 1 | import { env } from "~/lib/env/env"; 2 | 3 | export const createRedirectUrl = (redirectTo: string, searchParams?: Record) => { 4 | // it's a full url, just redirect them there 5 | if (URL.canParse(redirectTo)) { 6 | const url = new URL(redirectTo); 7 | Object.entries(searchParams ?? {}).forEach(([key, value]) => { 8 | url.searchParams.append(key, value); 9 | }); 10 | 11 | return url; 12 | } 13 | 14 | if (URL.canParse(redirectTo, env.PUBPUB_URL)) { 15 | const url = new URL(redirectTo, env.PUBPUB_URL); 16 | 17 | Object.entries(searchParams ?? {}).forEach(([key, value]) => { 18 | url.searchParams.append(key, value); 19 | }); 20 | 21 | return url; 22 | } 23 | 24 | // invalid redirectTo, redirect to not-found 25 | return new URL(`/not-found?from=${encodeURIComponent(redirectTo)}`, env.PUBPUB_URL); 26 | }; 27 | -------------------------------------------------------------------------------- /core/lib/server/cache/constants.ts: -------------------------------------------------------------------------------- 1 | export const PUBPUB_COMMUNITY_SLUG_COOKIE_NAME = "pubpub_community_slug" as const; 2 | 3 | export const PUBPUB_COMMUNITY_SLUG_HEADER_NAME = "x-pubpub-community-slug" as const; 4 | 5 | /** 6 | * In seconds 7 | */ 8 | export const ONE_DAY = 60 * 60 * 24; 9 | 10 | /** 11 | * In seconds 12 | */ 13 | export const ONE_YEAR = ONE_DAY * 365; 14 | -------------------------------------------------------------------------------- /core/lib/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./pub"; 2 | export * from "./errors"; 3 | export * from "./assets"; 4 | export * from "./pubtype"; 5 | -------------------------------------------------------------------------------- /core/lib/server/maybeWithTrx.ts: -------------------------------------------------------------------------------- 1 | import { Kysely, Transaction } from "kysely"; 2 | 3 | import type { Database } from "db/Database"; 4 | 5 | /** 6 | * For recursive transactions 7 | */ 8 | export const maybeWithTrx = async ( 9 | trx: Transaction | Kysely, 10 | fn: (trx: Transaction) => Promise 11 | ): Promise => { 12 | // could also use trx.isTransaction() 13 | if (trx instanceof Transaction) { 14 | return await fn(trx); 15 | } 16 | return await trx.transaction().execute(fn); 17 | }; 18 | -------------------------------------------------------------------------------- /core/lib/server/vitest.d.ts: -------------------------------------------------------------------------------- 1 | import "vitest"; 2 | 3 | import type { ProcessedPub } from "./pub"; 4 | import type { db } from "~/kysely/database"; 5 | 6 | interface CustomMatchers { 7 | toHaveValues(expected: Partial[]): R; 8 | toExist(expected?: typeof db): Promise; 9 | } 10 | 11 | declare module "vitest" { 12 | interface Assertion extends CustomMatchers {} 13 | interface AsymmetricMatchersContaining extends CustomMatchers {} 14 | } 15 | -------------------------------------------------------------------------------- /core/lib/ui.ts: -------------------------------------------------------------------------------- 1 | export const HEADER_HEIGHT = 72; 2 | -------------------------------------------------------------------------------- /core/load-test-flows.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-properties */ 2 | 3 | import type { Page } from "@playwright/test"; 4 | 5 | import * as loginFlows from "./playwright/login.flows"; 6 | 7 | export async function loginFlow(page: Page) { 8 | await loginFlows.login( 9 | page, 10 | process.env.LOAD_TEST_USER_EMAIL, 11 | process.env.LOAD_TEST_USER_PASSWORD 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /core/load-test.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | target: https://app.pubpub.org 3 | # Load the Playwright engine: 4 | engines: 5 | playwright: {} 6 | # Path to JavaScript file that defines Playwright test functions 7 | processor: "./load-test-flows.ts" 8 | phases: 9 | - duration: 60 10 | arrivalRate: 1 11 | rampTo: 2 12 | name: Warm up phase 13 | scenarios: 14 | - engine: playwright 15 | testFunction: "loginFlow" 16 | -------------------------------------------------------------------------------- /core/next.docker.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = { 2 | experimental: { 3 | instrumentationHook: true, 4 | }, 5 | }; 6 | 7 | module.exports = baseConfig; 8 | -------------------------------------------------------------------------------- /core/playwright/fixtures/login-page.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "@playwright/test"; 2 | 3 | import { waitForBaseCommunityPage } from "../helpers"; 4 | 5 | export class LoginPage { 6 | constructor(public readonly page: Page) {} 7 | 8 | async goto() { 9 | await this.page.goto("/login"); 10 | } 11 | 12 | async login(email: string, password: string) { 13 | await this.page.getByLabel("email").fill(email); 14 | await this.page.getByRole("textbox", { name: "password" }).fill(password); 15 | await this.page.getByRole("button", { name: "Sign in" }).click(); 16 | } 17 | 18 | async loginAndWaitForNavigation(email: string, password: string) { 19 | await this.login(email, password); 20 | // await this.page.waitForURL(/.*\/c\/.+\/stages.*/); 21 | await waitForBaseCommunityPage(this.page); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/playwright/fixtures/test-assets/test-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/playwright/fixtures/test-assets/test-diagram.png -------------------------------------------------------------------------------- /core/playwright/login.flows.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "@playwright/test"; 2 | 3 | import { LoginPage } from "./fixtures/login-page"; 4 | 5 | export const login = async (page: Page, email = "all@pubpub.org", password = "pubpub-all") => { 6 | const loginPage = new LoginPage(page); 7 | await loginPage.goto(); 8 | await loginPage.loginAndWaitForNavigation(email, password); 9 | }; 10 | -------------------------------------------------------------------------------- /core/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | 4 | module.exports = { 5 | plugins: { 6 | tailwindcss: {}, 7 | autoprefixer: {}, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230614132455_init_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "users" ( 3 | "id" TEXT NOT NULL, 4 | "slug" TEXT NOT NULL, 5 | "email" TEXT NOT NULL, 6 | "name" TEXT NOT NULL, 7 | "avatar" TEXT, 8 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | 11 | CONSTRAINT "users_pkey" PRIMARY KEY ("id") 12 | ); 13 | 14 | -- CreateIndex 15 | CREATE UNIQUE INDEX "users_slug_key" ON "users"("slug"); 16 | 17 | -- CreateIndex 18 | CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); 19 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230621173228_community_name/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `name` to the `communities` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "communities" ADD COLUMN "name" TEXT NOT NULL; 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230621174338_fix_pub_parent_required/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "pubs" DROP CONSTRAINT "pubs_parent_id_fkey"; 3 | 4 | -- AlterTable 5 | ALTER TABLE "pubs" ALTER COLUMN "parent_id" DROP NOT NULL; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "pubs" ADD CONSTRAINT "pubs_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "pubs"("id") ON DELETE SET NULL ON UPDATE CASCADE; 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230621175305_add_community_to_pub/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `community_id` to the `pubs` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "pubs" ADD COLUMN "community_id" TEXT NOT NULL; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "pubs" ADD CONSTRAINT "pubs_community_id_fkey" FOREIGN KEY ("community_id") REFERENCES "communities"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230622182626_blob_optional/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "pubs" ALTER COLUMN "metadataBlob" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230622195031_workflows_to_community/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `community_id` to the `workflows` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "workflows" ADD COLUMN "community_id" TEXT NOT NULL; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "workflows" ADD CONSTRAINT "workflows_community_id_fkey" FOREIGN KEY ("community_id") REFERENCES "communities"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230622195558_stage_names_order/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `name` to the `stages` table without a default value. This is not possible if the table is not empty. 5 | - Added the required column `order` to the `stages` table without a default value. This is not possible if the table is not empty. 6 | - Added the required column `name` to the `workflows` table without a default value. This is not possible if the table is not empty. 7 | 8 | */ 9 | -- AlterTable 10 | ALTER TABLE "stages" ADD COLUMN "name" TEXT NOT NULL, 11 | ADD COLUMN "order" TEXT NOT NULL; 12 | 13 | -- AlterTable 14 | ALTER TABLE "workflows" ADD COLUMN "name" TEXT NOT NULL; 15 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230706152501_integegration_field_ids/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "pub_fields" ADD COLUMN "integration_id" TEXT; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "pub_fields" ADD CONSTRAINT "pub_fields_integration_id_fkey" FOREIGN KEY ("integration_id") REFERENCES "integrations"("id") ON DELETE SET NULL ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230710105851_instance_community/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `community_id` to the `integration_instances` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "integration_instances" ADD COLUMN "community_id" TEXT NOT NULL; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "integration_instances" ADD CONSTRAINT "integration_instances_community_id_fkey" FOREIGN KEY ("community_id") REFERENCES "communities"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230712173822_community_avatar/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "communities" ADD COLUMN "avatar" TEXT; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230713124028_type_description/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "pub_types" ADD COLUMN "description" TEXT; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230730141617_community_slug_first/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "communities" ADD COLUMN "slug" TEXT; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230730141652_community_slug_final/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[slug]` on the table `communities` will be added. If there are existing duplicate values, this will fail. 5 | - Made the column `slug` on table `communities` required. This step will fail if there are existing NULL values in that column. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "communities" ALTER COLUMN "slug" SET NOT NULL; 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "communities_slug_key" ON "communities"("slug"); 13 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230906210115_add_auth_tokens/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "auth_tokens" ( 3 | "id" TEXT NOT NULL, 4 | "hash" TEXT NOT NULL, 5 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "expiresAt" TIMESTAMP(3) NOT NULL, 7 | "isUsed" BOOLEAN NOT NULL DEFAULT false, 8 | "user_id" TEXT NOT NULL, 9 | 10 | CONSTRAINT "auth_tokens_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "auth_tokens" ADD CONSTRAINT "auth_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 15 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230913200757_add_pub_hierarchy/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "pubs" ADD COLUMN "parent_id" TEXT; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "pubs" ADD CONSTRAINT "pubs_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "pubs"("id") ON DELETE SET NULL ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230919171747_add_pubfieldschema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "pub_fields" ADD COLUMN "pubFieldSchemaId" TEXT; 3 | 4 | -- CreateTable 5 | CREATE TABLE "PubFieldSchema" ( 6 | "id" TEXT NOT NULL, 7 | "namespace" TEXT NOT NULL, 8 | "name" TEXT NOT NULL, 9 | "schema" JSONB NOT NULL, 10 | 11 | CONSTRAINT "PubFieldSchema_pkey" PRIMARY KEY ("id") 12 | ); 13 | 14 | -- CreateIndex 15 | CREATE UNIQUE INDEX "PubFieldSchema_name_namespace_key" ON "PubFieldSchema"("name", "namespace"); 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "pub_fields" ADD CONSTRAINT "pub_fields_pubFieldSchemaId_fkey" FOREIGN KEY ("pubFieldSchemaId") REFERENCES "PubFieldSchema"("id") ON DELETE SET NULL ON UPDATE CASCADE; 19 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230925191147_add_pubfieldschema_dates/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "PubFieldSchema" ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 3 | ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 4 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230926234242_add_pubfield_slugs/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[slug]` on the table `pub_fields` will be added. If there are existing duplicate values, this will fail. 5 | - Added the required column `slug` to the `pub_fields` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "pub_fields" ADD COLUMN "slug" TEXT NOT NULL; 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "pub_fields_slug_key" ON "pub_fields"("slug"); 13 | -------------------------------------------------------------------------------- /core/prisma/migrations/20230928124422_user_name_first_last/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `name` on the `users` table. All the data in the column will be lost. 5 | - Added the required column `firstName` to the `users` table without a default value. This is not possible if the table is not empty. 6 | - Added the required column `lastName` to the `users` table without a default value. This is not possible if the table is not empty. 7 | 8 | */ 9 | -- AlterTable 10 | ALTER TABLE "users" 11 | ALTER COLUMN "name" SET DEFAULT '', 12 | ADD COLUMN "lastName" TEXT NOT NULL DEFAULT '', 13 | ADD COLUMN "orcid" TEXT; 14 | 15 | ALTER TABLE "users" RENAME COLUMN "name" TO "firstName"; 16 | -------------------------------------------------------------------------------- /core/prisma/migrations/20231010215802_add_supabase_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[supabaseId]` on the table `users` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "users" ADD COLUMN "supabaseId" TEXT; 9 | 10 | -- CreateIndex 11 | CREATE UNIQUE INDEX "users_supabaseId_key" ON "users"("supabaseId"); 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20231012092908_pub_delete_cascade/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "pub_values" DROP CONSTRAINT "pub_values_pub_id_fkey"; 3 | 4 | -- DropForeignKey 5 | ALTER TABLE "pubs" DROP CONSTRAINT "pubs_parent_id_fkey"; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "pubs" ADD CONSTRAINT "pubs_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "pubs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "pub_values" ADD CONSTRAINT "pub_values_pub_id_fkey" FOREIGN KEY ("pub_id") REFERENCES "pubs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20231016133737_nullable_last_name/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" ALTER COLUMN "lastName" DROP NOT NULL; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20231030180206_add_config_column_to_schema/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "integration_instances" ADD COLUMN "config" JSONB; 3 | 4 | -- AlterTable 5 | ALTER TABLE "users" ALTER COLUMN "firstName" DROP DEFAULT, 6 | ALTER COLUMN "lastName" DROP DEFAULT; 7 | -------------------------------------------------------------------------------- /core/prisma/migrations/20231031164256_add_integration_instance_state_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "IntegrationInstanceState" ( 3 | "pub_id" TEXT NOT NULL, 4 | "instance_id" TEXT NOT NULL, 5 | "value" JSONB NOT NULL 6 | ); 7 | 8 | -- CreateIndex 9 | CREATE UNIQUE INDEX "IntegrationInstanceState_pub_id_instance_id_key" ON "IntegrationInstanceState"("pub_id", "instance_id"); 10 | 11 | -- AddForeignKey 12 | ALTER TABLE "IntegrationInstanceState" ADD CONSTRAINT "IntegrationInstanceState_pub_id_fkey" FOREIGN KEY ("pub_id") REFERENCES "pubs"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "IntegrationInstanceState" ADD CONSTRAINT "IntegrationInstanceState_instance_id_fkey" FOREIGN KEY ("instance_id") REFERENCES "integration_instances"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 16 | -------------------------------------------------------------------------------- /core/prisma/migrations/20231107143830_change_to_state/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `value` on the `IntegrationInstanceState` table. All the data in the column will be lost. 5 | - Added the required column `state` to the `IntegrationInstanceState` table without a default value. This is not possible if the table is not empty. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "IntegrationInstanceState" DROP COLUMN "value", 10 | ADD COLUMN "state" JSONB NOT NULL; 11 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240117220109_add_unique_constraint_on_move_constraints/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[stage_id,destination_id]` on the table `move_constraint` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "move_constraint_stage_id_destination_id_key" ON "move_constraint"("stage_id", "destination_id"); 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240117220908_create_compound_move_constraint_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `move_constraint` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | - You are about to drop the column `id` on the `move_constraint` table. All the data in the column will be lost. 6 | 7 | */ 8 | -- DropIndex 9 | DROP INDEX "move_constraint_stage_id_destination_id_key"; 10 | 11 | -- AlterTable 12 | ALTER TABLE "move_constraint" DROP CONSTRAINT "move_constraint_pkey", 13 | DROP COLUMN "id", 14 | ADD CONSTRAINT "move_constraint_pkey" PRIMARY KEY ("stage_id", "destination_id"); 15 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240307023916_move_constraint_ondelete_cascade/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "move_constraint" DROP CONSTRAINT "move_constraint_destination_id_fkey"; 3 | 4 | -- DropForeignKey 5 | ALTER TABLE "move_constraint" DROP CONSTRAINT "move_constraint_stage_id_fkey"; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "move_constraint" ADD CONSTRAINT "move_constraint_stage_id_fkey" FOREIGN KEY ("stage_id") REFERENCES "stages"("id") ON DELETE CASCADE ON UPDATE CASCADE; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "move_constraint" ADD CONSTRAINT "move_constraint_destination_id_fkey" FOREIGN KEY ("destination_id") REFERENCES "stages"("id") ON DELETE CASCADE ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240314025159_add_assignee_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "pubs" ADD COLUMN "assignee_id" TEXT; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "pubs" ADD CONSTRAINT "pubs_assignee_id_fkey" FOREIGN KEY ("assignee_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240318203453_add_action_instance/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "action_instances" ( 3 | "id" TEXT NOT NULL, 4 | "action_id" TEXT NOT NULL, 5 | "stage_id" TEXT NOT NULL, 6 | "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | 9 | CONSTRAINT "action_instances_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "action_instances" ADD CONSTRAINT "action_instances_action_id_fkey" FOREIGN KEY ("action_id") REFERENCES "actions"("id") ON DELETE CASCADE ON UPDATE CASCADE; 14 | 15 | -- AddForeignKey 16 | ALTER TABLE "action_instances" ADD CONSTRAINT "action_instances_stage_id_fkey" FOREIGN KEY ("stage_id") REFERENCES "stages"("id") ON DELETE CASCADE ON UPDATE CASCADE; 17 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240328181858_add_action_description/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "actions" ADD COLUMN "description" TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240403211924_add_action_instance_config/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "action_instances" ADD COLUMN "config" JSONB; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240410194529_add_is_super_admin_to_user_model/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" ADD COLUMN "isSuperAdmin" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240410232644_add_explicit_pubs_to_stages_m_m_relationship/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "PubsInStages" ( 3 | "pubId" TEXT NOT NULL, 4 | "stageId" TEXT NOT NULL, 5 | 6 | CONSTRAINT "PubsInStages_pkey" PRIMARY KEY ("pubId","stageId") 7 | ); 8 | 9 | -- AddForeignKey 10 | ALTER TABLE "PubsInStages" ADD CONSTRAINT "PubsInStages_pubId_fkey" FOREIGN KEY ("pubId") REFERENCES "pubs"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "PubsInStages" ADD CONSTRAINT "PubsInStages_stageId_fkey" FOREIGN KEY ("stageId") REFERENCES "stages"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 14 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240410233228_remove_implicit_pubs_to_stages_relationship/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `_PubToStage` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | 8 | -- Copy Data 9 | INSERT INTO "PubsInStages" SELECT "A","B" FROM "_PubToStage"; 10 | -- DropForeignKey 11 | ALTER TABLE "_PubToStage" DROP CONSTRAINT "_PubToStage_A_fkey"; 12 | 13 | -- DropForeignKey 14 | ALTER TABLE "_PubToStage" DROP CONSTRAINT "_PubToStage_B_fkey"; 15 | 16 | -- DropTable 17 | DROP TABLE "_PubToStage"; 18 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240411012752_create_trigger_on_pub_stage_join_table/migration.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION emit_event() 2 | RETURNS TRIGGER AS 3 | $$ 4 | BEGIN 5 | PERFORM 6 | graphile_worker.add_job( 7 | 'emitEvent', 8 | json_build_object( 9 | 'table', TG_TABLE_NAME, 10 | 'operation', TG_OP, 11 | 'new', NEW, 12 | 'old', OLD 13 | ) 14 | ); 15 | RETURN NEW; 16 | END; 17 | $$ 18 | LANGUAGE plpgsql 19 | VOLATILE; 20 | 21 | CREATE TRIGGER pub_moved 22 | AFTER INSERT OR DELETE ON "PubsInStages" 23 | FOR EACH ROW 24 | EXECUTE FUNCTION emit_event(); 25 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240415174050_add_name_to_action_instances/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "action_instances" ADD COLUMN "name" TEXT NOT NULL DEFAULT ''; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240416111211_drop_actions_pubfields/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `actions` on the `pub_fields` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "pub_fields" DROP COLUMN "actions"; 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240416211436_add_push_to_v6_action/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "Action" ADD VALUE 'pushToV6'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240416214541_add_rules/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "Event" AS ENUM ('pubEnteredStage', 'pubLeftStage'); 3 | 4 | -- CreateTable 5 | CREATE TABLE "rules" ( 6 | "id" TEXT NOT NULL DEFAULT gen_random_uuid(), 7 | "event" "Event" NOT NULL, 8 | "action_instance_id" TEXT NOT NULL, 9 | 10 | CONSTRAINT "rules_pkey" PRIMARY KEY ("id") 11 | ); 12 | 13 | -- CreateIndex 14 | CREATE UNIQUE INDEX "rules_action_instance_id_event_key" ON "rules"("action_instance_id", "event"); 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "rules" ADD CONSTRAINT "rules_action_instance_id_fkey" FOREIGN KEY ("action_instance_id") REFERENCES "action_instances"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240416234734_add_cascade_on_delete/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "members" DROP CONSTRAINT "members_community_id_fkey"; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "members" ADD CONSTRAINT "members_community_id_fkey" FOREIGN KEY ("community_id") REFERENCES "communities"("id") ON DELETE CASCADE ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240418185908_remove_pubs_in_stages_if_pubs_or_stages_are_removed/migration.sql: -------------------------------------------------------------------------------- 1 | -- DropForeignKey 2 | ALTER TABLE "PubsInStages" DROP CONSTRAINT "PubsInStages_pubId_fkey"; 3 | 4 | -- DropForeignKey 5 | ALTER TABLE "PubsInStages" DROP CONSTRAINT "PubsInStages_stageId_fkey"; 6 | 7 | -- AddForeignKey 8 | ALTER TABLE "PubsInStages" ADD CONSTRAINT "PubsInStages_pubId_fkey" FOREIGN KEY ("pubId") REFERENCES "pubs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 9 | 10 | -- AddForeignKey 11 | ALTER TABLE "PubsInStages" ADD CONSTRAINT "PubsInStages_stageId_fkey" FOREIGN KEY ("stageId") REFERENCES "stages"("id") ON DELETE CASCADE ON UPDATE CASCADE; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240422152704_add_http_action/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "Action" ADD VALUE 'http'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240506184830_add_move_action/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "Action" ADD VALUE 'move'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240523104802_add_new_duration_event/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "Event" ADD VALUE 'pubInStageForDuration'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240523122445_add_config_option_for_rules/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "rules" ADD COLUMN "config" JSONB; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240529160343_record_action_rsult/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - Added the required column `result` to the `action_runs` table without a default value. This is not possible if the table is not empty. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "action_runs" ADD COLUMN "result" JSONB NOT NULL; 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240530112116_add_scheduled_action_run_status/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "ActionRunStatus" ADD VALUE 'scheduled'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240702114658_add_schemaname_column_to_pubfield/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "CoreSchemaType" AS ENUM ('String', 'Boolean', 'Vector3', 'DateTime', 'Email', 'URL', 'UserId', 'FileUpload'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "pub_fields" ADD COLUMN "schemaName" "CoreSchemaType"; 6 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240709044026_add_forms_archiving/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "forms" ADD COLUMN "isArchived" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240711145906_add_is_archived_to_pubfields/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "pub_fields" ADD COLUMN "isArchived" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240715133139_add_member_role_column/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "MemberRole" AS ENUM ('admin', 'editor', 'contributor'); 3 | 4 | -- AlterTable 5 | ALTER TABLE "members" 6 | ADD COLUMN "role" "MemberRole" NOT NULL DEFAULT 'editor'; 7 | 8 | -- Migrate members 9 | BEGIN; 10 | 11 | UPDATE "members" 12 | SET 13 | "role" = 'admin' 14 | WHERE 15 | "canAdmin" = true; 16 | 17 | COMMIT; -------------------------------------------------------------------------------- /core/prisma/migrations/20240715133257_remove_can_admin_column/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `canAdmin` on the `members` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "members" DROP COLUMN "canAdmin"; 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240717105403_add_role_to_membergroup/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "member_groups" 3 | ADD COLUMN "role" "MemberRole" NOT NULL DEFAULT 'editor'; 4 | 5 | -- Migrate data 6 | UPDATE "member_groups" 7 | set 8 | "role" = 'admin' 9 | where 10 | "canAdmin" = true; -------------------------------------------------------------------------------- /core/prisma/migrations/20240717105614_remove_can_admin_column_on_membergroup/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `canAdmin` on the `member_groups` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "member_groups" DROP COLUMN "canAdmin"; 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240725202915_enforce_unique_user_community_columns_in_members_table/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[userId,communityId]` on the table `members` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "members_userId_communityId_key" ON "members"("userId", "communityId"); 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240729202132_user_id_to_member_id/migration.sql: -------------------------------------------------------------------------------- 1 | ALTER TYPE "CoreSchemaType" RENAME VALUE 'UserId' TO 'MemberId' 2 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240731145045_remove_old_references_in_schemas/migration.sql: -------------------------------------------------------------------------------- 1 | UPDATE 2 | "PubFieldSchema" 3 | SET 4 | schema = jsonb_set(schema, '{$id}', concat('"unjournal:', split_part(schema ->> '$id', ':', 2), '"')::jsonb) 5 | WHERE 6 | split_part(schema ->> '$id', ':', 1) = 'pubpub' 7 | RETURNING 8 | schema; 9 | 10 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240806164513_add_password_hash_and_salt_to_users_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "users" 3 | ADD COLUMN "passwordHash" TEXT; -------------------------------------------------------------------------------- /core/prisma/migrations/20240806170730_add_sessions_table/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE 3 | "sessions" ( 4 | "id" TEXT NOT NULL DEFAULT gen_random_uuid (), 5 | "userId" TEXT NOT NULL, 6 | "expiresAt" TIMESTAMP(3) NOT NULL, 7 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 8 | "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | CONSTRAINT "sessions_pkey" PRIMARY KEY ("id") 10 | ); 11 | 12 | -- AddForeignKey 13 | ALTER TABLE "sessions" ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; -------------------------------------------------------------------------------- /core/prisma/migrations/20240819104844_add_authtokentype_to_both_session_and_authtoken/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "AuthTokenType" AS ENUM ( 3 | 'generic', 4 | 'passwordReset', 5 | 'signup', 6 | 'verifyEmail' 7 | ); 8 | 9 | -- AlterTable 10 | ALTER TABLE "auth_tokens" 11 | ADD COLUMN "type" "AuthTokenType" NOT NULL DEFAULT 'generic', 12 | ALTER COLUMN "id" 13 | SET DEFAULT gen_random_uuid (); 14 | 15 | -- AlterTable 16 | ALTER TABLE "sessions" 17 | ADD COLUMN "type" "AuthTokenType" NOT NULL DEFAULT 'generic'; -------------------------------------------------------------------------------- /core/prisma/migrations/20240829135909_rename_unjournal_fields/migration.sql: -------------------------------------------------------------------------------- 1 | -- Add "legacy" to fields that don't have schemas and archive 2 | UPDATE 3 | pub_fields 4 | SET 5 | name = '(Legacy) ' || name, 6 | slug = 'legacy-' || slug, 7 | "isArchived" = true 8 | WHERE 9 | "schemaName" IS null; 10 | 11 | -- Replace field names in instance config 12 | UPDATE 13 | integration_instances 14 | SET 15 | config = replace( 16 | config :: TEXT, 17 | 'unjournal:', 18 | 'legacy-unjournal:' 19 | ) :: jsonb; 20 | 21 | -- Remove form elements tied to schemaless fields 22 | DELETE FROM 23 | form_elements USING pub_fields 24 | WHERE 25 | "form_elements"."fieldId" IS NOT null 26 | AND "pub_fields"."schemaName" IS null; -------------------------------------------------------------------------------- /core/prisma/migrations/20240829182113_remove_supabase_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `supabaseId` on the `users` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropIndex 8 | DROP INDEX "users_supabaseId_key"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "users" DROP COLUMN "supabaseId"; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240917200221_add_relationships_to_pub_values/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[pubId,relatedPubId,fieldId]` on the table `pub_values` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "pub_fields" ADD COLUMN "isRelation" BOOLEAN NOT NULL DEFAULT false; 9 | 10 | -- AlterTable 11 | ALTER TABLE "pub_values" ADD COLUMN "relatedPubId" TEXT, 12 | ALTER COLUMN "value" DROP NOT NULL; 13 | 14 | -- CreateIndex 15 | CREATE UNIQUE INDEX "pub_values_pubId_relatedPubId_fieldId_key" ON "pub_values"("pubId", "relatedPubId", "fieldId"); 16 | 17 | -- AddForeignKey 18 | ALTER TABLE "pub_values" ADD CONSTRAINT "pub_values_relatedPubId_fkey" FOREIGN KEY ("relatedPubId") REFERENCES "pubs"("id") ON DELETE CASCADE ON UPDATE CASCADE; 19 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240923161145_add_null_core_schema_type_and_make_pubvalues_unique_constraint_not_null_unique/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "CoreSchemaType" ADD VALUE 'Null'; 3 | 4 | -- Alter the unique constraint so that Nulls are not distinct 5 | -- We want to be able to still have a unique constraint on just pubId and fieldId if there is a null relatedPubId 6 | BEGIN; 7 | DROP INDEX "pub_values_pubId_relatedPubId_fieldId_key"; 8 | CREATE UNIQUE INDEX "pub_values_pubId_relatedPubId_fieldId_key" ON "pub_values"("pubId", "relatedPubId", "fieldId") 9 | WHERE "relatedPubId" IS NOT NULL; 10 | 11 | CREATE UNIQUE INDEX "pub_values_pubId_fieldId_key" ON "pub_values"("pubId", "fieldId") 12 | WHERE "relatedPubId" IS NULL; 13 | COMMIT; 14 | -------------------------------------------------------------------------------- /core/prisma/migrations/20240930150006_add_number_numeric_string_array/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | -- This migration adds more than one value to an enum. 3 | -- With PostgreSQL versions 11 and earlier, this is not possible 4 | -- in a single migration. This can be worked around by creating 5 | -- multiple migrations, each migration adding only one value to 6 | -- the enum. 7 | 8 | 9 | ALTER TYPE "CoreSchemaType" ADD VALUE 'Number'; 10 | ALTER TYPE "CoreSchemaType" ADD VALUE 'NumericArray'; 11 | ALTER TYPE "CoreSchemaType" ADD VALUE 'StringArray'; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20241002220742_remove_label_column_from_inputs/migration.sql: -------------------------------------------------------------------------------- 1 | -- Move existing labels into the config object 2 | UPDATE "form_elements" 3 | SET config = jsonb_build_object('label', "form_elements"."label") 4 | WHERE component != 'checkbox'::"InputComponent" AND type = 'pubfield'::"ElementType"; 5 | UPDATE "form_elements" 6 | SET config = jsonb_build_object('groupLabel', "form_elements"."label") 7 | WHERE component = 'checkbox'::"InputComponent" AND type = 'pubfield'::"ElementType"; 8 | -------------------------------------------------------------------------------- /core/prisma/migrations/20241007212932_add_more_options_to_input_component_enum/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | -- This migration adds more than one value to an enum. 3 | -- With PostgreSQL versions 11 and earlier, this is not possible 4 | -- in a single migration. This can be worked around by creating 5 | -- multiple migrations, each migration adding only one value to 6 | -- the enum. 7 | 8 | 9 | ALTER TYPE "InputComponent" ADD VALUE 'checkboxGroup'; 10 | ALTER TYPE "InputComponent" ADD VALUE 'radioGroup'; 11 | ALTER TYPE "InputComponent" ADD VALUE 'selectDropdown'; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20241016174257_add_multivalue_input_to_input_component_enum/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "InputComponent" ADD VALUE 'multivalueInput'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20241021151230_add_rich_text_type/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "CoreSchemaType" ADD VALUE 'RichText'; 3 | 4 | -- AlterEnum 5 | ALTER TYPE "InputComponent" ADD VALUE 'richText'; 6 | -------------------------------------------------------------------------------- /core/prisma/migrations/20241105184158_copy_membership_data/migration.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | INSERT INTO "community_memberships" ("id", "role", "communityId", "userId", "createdAt", "updatedAt") 4 | SELECT "id", "role", "communityId", "userId", "createdAt", "updatedAt" FROM "members" 5 | ON CONFLICT ("id") DO NOTHING; 6 | 7 | INSERT INTO "form_memberships" ("formId", "userId") 8 | SELECT "formId", "userId" FROM "form_to_permissions" 9 | JOIN "permissions" 10 | ON "permissions"."id" = "form_to_permissions"."permissionId" 11 | JOIN "members" 12 | ON "members"."id" = "permissions"."memberId" 13 | ON CONFLICT DO NOTHING; 14 | 15 | COMMIT; -------------------------------------------------------------------------------- /core/prisma/migrations/20241126113759_add_pub_values_updated_at_trigger/migration.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION update_pub_updated_at() 2 | RETURNS TRIGGER AS $$ 3 | BEGIN 4 | UPDATE "pubs" 5 | -- it's fine to use CURRENT_TIMESTAMP here because we're inside a transaction 6 | -- and the timestamp will be the same for all rows in the transaction 7 | SET "updatedAt" = CURRENT_TIMESTAMP 8 | WHERE "id" = CASE 9 | WHEN TG_OP = 'DELETE' THEN OLD."pubId" 10 | ELSE NEW."pubId" 11 | END; 12 | RETURN NULL; 13 | END; 14 | $$ LANGUAGE plpgsql; 15 | 16 | CREATE TRIGGER trigger_pub_values_update_pub 17 | AFTER INSERT OR UPDATE OR DELETE ON "pub_values" 18 | FOR EACH ROW 19 | EXECUTE FUNCTION update_pub_updated_at(); -------------------------------------------------------------------------------- /core/prisma/migrations/20241126151624_add_google_drive_import_action/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "Action" ADD VALUE 'googleDriveImport'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20241203035210_add_default_forms/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "forms" ADD COLUMN "isDefault" BOOLEAN NOT NULL DEFAULT false; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20241203193207_add_datacite_action/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "Action" ADD VALUE 'datacite'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20241205192106_unique_pub_type_names/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[name,communityId]` on the table `pub_types` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "pub_types_name_communityId_key" ON "pub_types"("name", "communityId"); 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250114195812_add_relation_block_input_component/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "InputComponent" ADD VALUE 'relationBlock'; 3 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250205172301_stage_updated_at_trigger/migration.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION update_pub_updated_at() 2 | RETURNS TRIGGER AS $$ 3 | BEGIN 4 | UPDATE "pubs" 5 | -- it's fine to use CURRENT_TIMESTAMP here because we're inside a transaction 6 | -- and the timestamp will be the same for all rows in the transaction 7 | SET "updatedAt" = CURRENT_TIMESTAMP 8 | WHERE "id" = CASE 9 | WHEN TG_OP = 'DELETE' THEN OLD."pubId" 10 | ELSE NEW."pubId" 11 | END; 12 | RETURN NULL; 13 | END; 14 | $$ LANGUAGE plpgsql; 15 | 16 | CREATE TRIGGER trigger_pubs_in_stages_update_pub 17 | AFTER INSERT OR UPDATE OR DELETE ON "PubsInStages" 18 | FOR EACH ROW 19 | EXECUTE FUNCTION update_pub_updated_at(); -------------------------------------------------------------------------------- /core/prisma/migrations/20250227001152_let_community_editors_invite_users/migration.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO 2 | "membership_capabilities" 3 | VALUES 4 | ( 5 | 'editor'::"MemberRole", 6 | 'community'::"MembershipType", 7 | 'addCommunityMember'::"Capabilities" 8 | ); -------------------------------------------------------------------------------- /core/prisma/migrations/20250304132049_add_action_success_events/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | -- This migration adds more than one value to an enum. 3 | -- With PostgreSQL versions 11 and earlier, this is not possible 4 | -- in a single migration. This can be worked around by creating 5 | -- multiple migrations, each migration adding only one value to 6 | -- the enum. 7 | 8 | 9 | ALTER TYPE "Event" ADD VALUE 'actionSucceeded'; 10 | ALTER TYPE "Event" ADD VALUE 'actionFailed'; 11 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250305135055_add_action_ref_to_rul/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "rules" ADD COLUMN "sourceActionInstanceId" TEXT; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "rules" ADD CONSTRAINT "rules_sourceActionInstanceId_fkey" FOREIGN KEY ("sourceActionInstanceId") REFERENCES "action_instances"("id") ON DELETE SET NULL ON UPDATE CASCADE; 6 | 7 | DROP INDEX "rules_actionInstanceId_event_key"; 8 | 9 | -- unique index for action chaining events 10 | CREATE UNIQUE INDEX "unique_action_chaining_events" ON "rules" ("actionInstanceId", "event", "sourceActionInstanceId") WHERE "sourceActionInstanceId" IS NOT NULL; 11 | 12 | -- unique index for regular events 13 | CREATE UNIQUE INDEX "unique_regular_events" ON "rules" ("actionInstanceId", "event") WHERE "sourceActionInstanceId" IS NULL; -------------------------------------------------------------------------------- /core/prisma/migrations/20250306165526_log_triggering_action_run/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | ALTER TABLE "action_runs" ADD COLUMN "sourceActionRunId" TEXT; 3 | 4 | -- AddForeignKey 5 | ALTER TABLE "action_runs" ADD CONSTRAINT "action_runs_sourceActionRunId_fkey" FOREIGN KEY ("sourceActionRunId") REFERENCES "action_runs"("id") ON DELETE SET NULL ON UPDATE CASCADE; 6 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250306184110_remove_parent_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `parentId` on the `pubs` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "pubs" DROP CONSTRAINT "pubs_parentId_fkey"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "pubs" DROP COLUMN "parentId"; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250313200801_form_is_default_unique_constraint/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[isDefault,pubTypeId]` on the table `forms` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "forms_isDefault_pubTypeId_key" ON "forms"("isDefault", "pubTypeId") WHERE "forms"."isDefault" is true; 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250319022702_remove_order/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `order` on the `form_elements` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "form_elements" DROP COLUMN "order"; 9 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250327190100_remove_invite_only_form_access_option/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The values [inviteOnly] on the enum `FormAccessType` will be removed. If these variants are still used in the database, this will fail. 5 | 6 | */ 7 | -- AlterEnum 8 | BEGIN; 9 | 10 | UPDATE "forms" SET "access" = 'private' WHERE "access" = 'inviteOnly'; 11 | 12 | CREATE TYPE "FormAccessType_new" AS ENUM ('private', 'public'); 13 | ALTER TABLE "forms" ALTER COLUMN "access" DROP DEFAULT; 14 | ALTER TABLE "forms" ALTER COLUMN "access" TYPE "FormAccessType_new" USING ("access"::text::"FormAccessType_new"); 15 | ALTER TYPE "FormAccessType" RENAME TO "FormAccessType_old"; 16 | ALTER TYPE "FormAccessType_new" RENAME TO "FormAccessType"; 17 | DROP TYPE "FormAccessType_old"; 18 | ALTER TABLE "forms" ALTER COLUMN "access" SET DEFAULT 'private'; 19 | COMMIT; 20 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250331203256_add_related_pub_types_to_form_elements/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "_FormElementToPubType" ( 3 | "A" TEXT NOT NULL, 4 | "B" TEXT NOT NULL 5 | ); 6 | 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "_FormElementToPubType_AB_unique" ON "_FormElementToPubType"("A", "B"); 9 | 10 | -- CreateIndex 11 | CREATE INDEX "_FormElementToPubType_B_index" ON "_FormElementToPubType"("B"); 12 | 13 | -- AddForeignKey 14 | ALTER TABLE "_FormElementToPubType" ADD CONSTRAINT "_FormElementToPubType_A_fkey" FOREIGN KEY ("A") REFERENCES "form_elements"("id") ON DELETE CASCADE ON UPDATE CASCADE; 15 | 16 | -- AddForeignKey 17 | ALTER TABLE "_FormElementToPubType" ADD CONSTRAINT "_FormElementToPubType_B_fkey" FOREIGN KEY ("B") REFERENCES "pub_types"("id") ON DELETE CASCADE ON UPDATE CASCADE; 18 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250407203301_add_is_verified_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterTable 2 | 3 | ALTER TABLE "users" ADD COLUMN "isVerified" BOOLEAN NOT NULL DEFAULT false; 4 | 5 | -- Set existing users to verified 6 | UPDATE "users" SET "isVerified" = true; -------------------------------------------------------------------------------- /core/prisma/migrations/20250416145507_drop_assignee/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `assigneeId` on the `pubs` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "pubs" DROP CONSTRAINT "pubs_assigneeId_fkey"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "pubs" DROP COLUMN "assigneeId"; 12 | -------------------------------------------------------------------------------- /core/prisma/migrations/20250512144047_clear_richtext_values/migration.sql: -------------------------------------------------------------------------------- 1 | UPDATE "pub_values" SET "value" = '"

"', "lastModifiedBy" = CONCAT('system', '|', FLOOR(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000)) WHERE "fieldId" IN ( 2 | SELECT "id" FROM "pub_fields" WHERE "schemaName" = 'RichText'::"CoreSchemaType" 3 | ); -------------------------------------------------------------------------------- /core/prisma/migrations/20250513181701_add_color_type/migration.sql: -------------------------------------------------------------------------------- 1 | -- AlterEnum 2 | ALTER TYPE "CoreSchemaType" 3 | ADD VALUE 'Color'; 4 | 5 | ALTER TYPE "InputComponent" 6 | ADD VALUE 'colorPicker'; 7 | 8 | -------------------------------------------------------------------------------- /core/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /core/prisma/seed/seed-wrapper.cts: -------------------------------------------------------------------------------- 1 | // this weird setup with the `cts` is to trick `tsx` into using CommonJS modules, even though we are using type: module 2 | // because we want to stub out things in `register-stub.cjs` but that only works with CommonJS modules 3 | 4 | require("../seed"); 5 | -------------------------------------------------------------------------------- /core/prisma/seed/stubs/stubs.js: -------------------------------------------------------------------------------- 1 | const stubFn = (fn) => fn; 2 | 3 | export { 4 | stubFn as cache, 5 | stubFn as useId, 6 | stubFn as forwardRef, 7 | stubFn as autoCache, 8 | stubFn as autoRevalidate, 9 | stubFn as NextResponse, 10 | stubFn as cookies, 11 | stubFn as headers, 12 | stubFn as getPathname, 13 | stubFn as getSearchParams, 14 | stubFn as getUrl, 15 | stubFn as getRequest, 16 | stubFn as getResponse, 17 | stubFn as getParams, 18 | stubFn as redirect, 19 | stubFn as unstable_cache, 20 | stubFn as Suspense, 21 | stubFn as default, 22 | stubFn as reactErrorHandler, 23 | stubFn as withSentryConfig, 24 | }; 25 | -------------------------------------------------------------------------------- /core/project.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "project-root", 5 | "path": "./", 6 | }, 7 | ], 8 | } 9 | -------------------------------------------------------------------------------- /core/public/demo/arcadia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/public/demo/arcadia.png -------------------------------------------------------------------------------- /core/public/demo/biorxiv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/public/demo/biorxiv.png -------------------------------------------------------------------------------- /core/public/demo/brown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/public/demo/brown.png -------------------------------------------------------------------------------- /core/public/demo/croc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/public/demo/croc.png -------------------------------------------------------------------------------- /core/public/demo/mitp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/public/demo/mitp.jpg -------------------------------------------------------------------------------- /core/public/demo/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/public/demo/person.png -------------------------------------------------------------------------------- /core/public/demo/unjournal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/core/public/demo/unjournal.png -------------------------------------------------------------------------------- /core/public/icons/chevron-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/public/icons/dashboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /core/public/icons/ellipsis.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/public/icons/form.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /core/public/icons/pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/public/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/public/logos/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /core/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const sharedConfig = require("ui/tailwind.config.cjs"); 3 | // const editorConfig = require("context-editor/tailwind.config.cjs"); 4 | const packagePath = (id) => path.dirname(require.resolve(`${id}/package.json`)); 5 | const packageSource = (id) => path.join(packagePath(id), "src", "**/*.{ts,tsx}"); 6 | 7 | /** @type {import('tailwindcss').Config} */ 8 | module.exports = { 9 | presets: [sharedConfig], 10 | plugins: [require("@tailwindcss/forms")], 11 | content: [ 12 | "./app/**/*.{ts,tsx}", 13 | "./actions/**/*.{ts,tsx}", 14 | packageSource("ui"), 15 | packageSource("context-editor"), 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /core/types/preconstruct_next.ts: -------------------------------------------------------------------------------- 1 | declare module "@preconstruct/next" { 2 | import type { NextConfig } from "next"; 3 | export default function withPreconstruct(nextConfig: NextConfig): NextConfig; 4 | } 5 | -------------------------------------------------------------------------------- /docker-compose.preview.pr.yml: -------------------------------------------------------------------------------- 1 | services: 2 | platform: 3 | environment: 4 | PUBPUB_URL: ${PULLPREVIEW_URL} 5 | caddy: 6 | environment: 7 | PUBLIC_URL: ${PULLPREVIEW_PUBLIC_DNS} 8 | -------------------------------------------------------------------------------- /docker-compose.preview.sandbox.yml: -------------------------------------------------------------------------------- 1 | services: 2 | platform: 3 | environment: 4 | PUBPUB_URL: https://sandbox.pubpub.org 5 | caddy: 6 | environment: 7 | PUBLIC_URL: sandbox.pubpub.org 8 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | _pagefind/ 44 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # PubPub Development Documentation 2 | 3 | This is the development documentation for PubPub, mostly intended for internal use. 4 | 5 | For documentation on how to use PubPub, see . 6 | -------------------------------------------------------------------------------- /docs/content/_meta.ts: -------------------------------------------------------------------------------- 1 | import type { MetaRecord} from 'nextra' 2 | 3 | const meta: MetaRecord = { 4 | infrastructure: "🏗️ Infrastructure", 5 | }; 6 | 7 | export default meta; 8 | -------------------------------------------------------------------------------- /docs/content/development/richtext.mdx: -------------------------------------------------------------------------------- 1 | # Rich Text Fields 2 | 3 | - `RichText` fields are stored as html in the database. not as prosemirror trees anymore. 4 | - The `ContextEditor` only know about ProseMirror trees. 5 | - The "boundary" where HTML gets converted to a ProseMirror tree is in `PubEditor.tsx`, on the server. The `PubEditor` form handles `ProseMirror` trees, and sends that up to the server as well (this could be changed to send html instead, but that would require extra conversion logic on the server). 6 | - Most clients (like the site-building api) just work with html. they never need to know about ProseMirror. only the main app really needs to care about ProseMirror, and only for editing. 7 | - If you want to create new content, you can just send html. the server will check that it converts to a valid ProseMirror tree, and if so, it'll store it as-is. 8 | -------------------------------------------------------------------------------- /docs/content/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction" 3 | --- 4 | 5 | # Introduction 6 | 7 | This is the developer documentation for PubPub Platform. 8 | 9 | Internal use. 10 | -------------------------------------------------------------------------------- /docs/content/infrastructure/environments/global-aws.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Global AWS configurations" 3 | --- 4 | 5 | # Global AWS configurations 6 | 7 | This module should generally be created by an admin, 8 | and not applied or updated by a machine user. 9 | 10 | ## Creation of the terraform state bucket 11 | 12 | 1. Uncomment the code creating this bucket; comment the backend block 13 | 1. terraform init 14 | 1. Set the bucket name 15 | 1. terraform apply 16 | 1. `terraform state rm aws_s3_bucket.terraform_state` 17 | 1. comment the bucket definition; uncomment the backend block 18 | 1. terraform init ("yes" to copying the state file) 19 | 1. destroy local copies of the state file 20 | 21 | This bucket name can now be in your s3.tfbackend files everywhere. 22 | -------------------------------------------------------------------------------- /docs/content/infrastructure/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Infrastructure" 3 | description: "Information about the infrastructure that runs PubPub." 4 | sidebarTitle: "🏗️ Infrastructure" 5 | asIndexPage: true 6 | --- 7 | 8 | # Infrastructure 9 | 10 | Information about the infrastructure that runs PubPub 11 | -------------------------------------------------------------------------------- /docs/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import baseConfig, { restrictEnvAccess } from "@pubpub/eslint-config/base"; 2 | import nextConfig from "@pubpub/eslint-config/next"; 3 | import reactConfig from "@pubpub/eslint-config/react"; 4 | 5 | /** @type {import('typescript-eslint').Config} */ 6 | export default [ 7 | { 8 | ignores: [ 9 | ".next/**", 10 | "playwright-report/**", 11 | "playwright/.auth", 12 | "test-results/**", 13 | "**/*stub*.js", 14 | ], 15 | }, 16 | ...baseConfig, 17 | ...reactConfig, 18 | ...nextConfig, 19 | ...restrictEnvAccess, 20 | ]; 21 | -------------------------------------------------------------------------------- /docs/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import { useMDXComponents as getThemeComponents } from "nextra-theme-docs"; // nextra-theme-blog or your custom theme 2 | 3 | // Get the default MDX components 4 | const themeComponents = getThemeComponents(); 5 | 6 | // Merge components 7 | export function useMDXComponents(components?: Record>) { 8 | return { 9 | ...themeComponents, 10 | ...components, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /docs/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | import nextra from "nextra"; 4 | 5 | import { path } from "./utils/path"; 6 | 7 | const withNextra = nextra({ 8 | search: true, 9 | mdxOptions: { 10 | rehypePrettyCodeOptions: { 11 | // 12 | }, 13 | }, 14 | }); 15 | 16 | const nextConfig: NextConfig = withNextra({ 17 | output: "export", 18 | basePath: path(""), 19 | typescript: { 20 | ignoreBuildErrors: true, 21 | }, 22 | eslint: { 23 | ignoreDuringBuilds: true, 24 | }, 25 | experimental: { 26 | parallelServerBuildTraces: true, 27 | webpackBuildWorker: true, 28 | }, 29 | }); 30 | 31 | export default nextConfig; 32 | -------------------------------------------------------------------------------- /docs/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /docs/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/docs/src/app/favicon.ico -------------------------------------------------------------------------------- /docs/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme { 4 | --font-sans: var(--font-geist-sans); 5 | --font-mono: var(--font-geist-mono); 6 | } 7 | 8 | :root { 9 | --background: #ffffff; 10 | --foreground: #171717; 11 | } 12 | 13 | @media (prefers-color-scheme: dark) { 14 | :root { 15 | --background: #0a0a0a; 16 | --foreground: #ededed; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/app/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /docs/utils/path.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-properties */ 2 | 3 | export const path = (path: string) => { 4 | const prefix = 5 | process.env.NODE_ENV === "production" 6 | ? `/${process.env.PREFIX || "platform"}${process.env.PR_NUMBER ? `/pr-preview/pr-${process.env.PR_NUMBER}` : ""}` 7 | : ""; 8 | return `${prefix}${path}`; 9 | }; 10 | -------------------------------------------------------------------------------- /infrastructure/Brewfile: -------------------------------------------------------------------------------- 1 | # infrastructure dependencies 2 | brew "mask" 3 | brew "awscli" 4 | brew "terraform" 5 | brew "act" 6 | 7 | cask "session-manager-plugin" 8 | -------------------------------------------------------------------------------- /infrastructure/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | COPY default.conf.template /etc/nginx/templates/default.conf.template 3 | -------------------------------------------------------------------------------- /infrastructure/nginx/default.conf.template: -------------------------------------------------------------------------------- 1 | upstream nextjs { 2 | server ${NGINX_UPSTREAM_HOST}:${NGINX_UPSTREAM_PORT}; 3 | } 4 | 5 | server { 6 | listen ${NGINX_LISTEN_PORT}; 7 | server_name _; 8 | 9 | location / { 10 | proxy_pass $scheme://nextjs; 11 | 12 | proxy_set_header Host $host; 13 | proxy_set_header X-Forwarded-Host $host; 14 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 15 | proxy_set_header X-Real-IP $remote_addr; 16 | } 17 | 18 | location /legacy_healthcheck { 19 | return 200; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /infrastructure/terraform/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.tfstate 2 | **/*.tfstate.* 3 | **/.terraform 4 | **/secrets.tfvars 5 | -------------------------------------------------------------------------------- /infrastructure/terraform/environments/cloudflare/outputs.tf: -------------------------------------------------------------------------------- 1 | output "route53_zones" { 2 | value = { 3 | "pubpub.org" = aws_route53_zone.pubpub.zone_id 4 | "duqduq.org" = aws_route53_zone.duqduq.zone_id 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /infrastructure/terraform/environments/global_aws/README.md: -------------------------------------------------------------------------------- 1 | # Global configurations 2 | 3 | This module should generally be created by an admin, 4 | and not applied or updated by a machine user. 5 | 6 | ## Creation of the terraform state bucket 7 | 8 | 1. Uncomment the code creating this bucket; comment the backend block 9 | 1. terraform init 10 | 1. Set the bucket name 11 | 1. terraform apply 12 | 1. `terraform state rm aws_s3_bucket.terraform_state` 13 | 1. comment the bucket definition; uncomment the backend block 14 | 1. terraform init ("yes" to copying the state file) 15 | 1. destroy local copies of the state file 16 | 17 | This bucket name can now be in your s3.tfbackend files everywhere. 18 | -------------------------------------------------------------------------------- /infrastructure/terraform/environments/global_aws/outputs.tf: -------------------------------------------------------------------------------- 1 | # if resources are needed in an environment's inputs, 2 | # then you can use terraform_remote_state to get this module's output 3 | 4 | # put these creds in github actions secrets config 5 | output "github_actions_user_credential" { 6 | value = { 7 | id = aws_iam_access_key.github_actions.id 8 | secret = aws_iam_access_key.github_actions.secret 9 | } 10 | 11 | # prevents this secret value from appearing accidentally 12 | # NOTE - it is still saved in the state file. 13 | sensitive = true 14 | } 15 | 16 | # Provide this value to github actions 17 | output "github_actions_role_to_assume_arn" { 18 | value = aws_iam_role.github_actions_role.arn 19 | } 20 | 21 | -------------------------------------------------------------------------------- /infrastructure/terraform/modules/container-generic/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/infrastructure/terraform/modules/container-generic/README.md -------------------------------------------------------------------------------- /infrastructure/terraform/modules/container-generic/outputs.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/infrastructure/terraform/modules/container-generic/outputs.tf -------------------------------------------------------------------------------- /infrastructure/terraform/modules/core-services/variables.tf: -------------------------------------------------------------------------------- 1 | variable "cluster_info" { 2 | description = "infrastructure values output from v7-cluster" 3 | 4 | type = object({ 5 | region = string 6 | name = string 7 | vpc_id = string 8 | cluster_arn = string 9 | environment = string 10 | private_subnet_ids = list(string) 11 | container_security_group_ids = list(string) 12 | cloudwatch_log_group_name = string 13 | lb_listener_arn = string 14 | }) 15 | } 16 | 17 | variable "assets_bucket_url_name" { 18 | description = "Name for the asset bucket -- typically a domain like assets.v7.pubpub.org" 19 | type = string 20 | } 21 | -------------------------------------------------------------------------------- /infrastructure/terraform/modules/deployment/outputs.tf: -------------------------------------------------------------------------------- 1 | output "cluster_info" { 2 | value = module.cluster.cluster_info 3 | } 4 | -------------------------------------------------------------------------------- /infrastructure/terraform/modules/ecr-repositories/outputs.tf: -------------------------------------------------------------------------------- 1 | output "ecr_repository_urls" { 2 | value = { 3 | core = aws_ecr_repository.pubpub_v7_core.repository_url 4 | jobs = aws_ecr_repository.pubpub_v7_jobs.repository_url 5 | nginx = aws_ecr_repository.nginx.repository_url 6 | root = aws_ecr_repository.pubpub_v7.repository_url 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /infrastructure/terraform/modules/ecr-repositories/variables.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/infrastructure/terraform/modules/ecr-repositories/variables.tf -------------------------------------------------------------------------------- /infrastructure/terraform/modules/honeycomb-integration/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/infrastructure/terraform/modules/honeycomb-integration/README.md -------------------------------------------------------------------------------- /infrastructure/terraform/modules/honeycomb-integration/outputs.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/infrastructure/terraform/modules/honeycomb-integration/outputs.tf -------------------------------------------------------------------------------- /infrastructure/terraform/modules/honeycomb-integration/variables.tf: -------------------------------------------------------------------------------- 1 | variable "cluster_info" { 2 | description = "infrastructure values output from v7-cluster" 3 | 4 | type = object({ 5 | region = string 6 | name = string 7 | vpc_id = string 8 | cluster_arn = string 9 | environment = string 10 | private_subnet_ids = list(string) 11 | container_security_group_ids = list(string) 12 | cloudwatch_log_group_name = string 13 | lb_listener_arn = string 14 | }) 15 | } 16 | 17 | variable "HONEYCOMB_API_KEY" { 18 | description = "API key for the honeycomb environment" 19 | type = string 20 | sensitive = true 21 | } 22 | -------------------------------------------------------------------------------- /infrastructure/terraform/modules/v7-cluster/dns.tf: -------------------------------------------------------------------------------- 1 | module "alb_certificate" { 2 | source = "terraform-aws-modules/acm/aws" 3 | version = "~> 4.0" 4 | 5 | domain_name = var.pubpub_hostname 6 | zone_id = var.route53_zone_id 7 | 8 | validation_method = "DNS" 9 | 10 | subject_alternative_names = [ 11 | "*.${var.pubpub_hostname}", 12 | ] 13 | 14 | wait_for_validation = true 15 | 16 | tags = { 17 | Name = var.pubpub_hostname 18 | Environment = var.environment 19 | } 20 | } 21 | 22 | resource "aws_route53_record" "alb" { 23 | zone_id = var.route53_zone_id 24 | name = var.pubpub_hostname 25 | type = "A" 26 | 27 | alias { 28 | name = aws_lb.main.dns_name 29 | zone_id = aws_lb.main.zone_id 30 | evaluate_target_health = false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /infrastructure/terraform/modules/v7-cluster/ecs.tf: -------------------------------------------------------------------------------- 1 | module "ecs_cluster" { 2 | source = "terraform-aws-modules/ecs/aws//modules/cluster" 3 | 4 | cluster_name = "${var.name}-ecs-cluster-${var.environment}" 5 | 6 | cluster_configuration = { 7 | execute_command_configuration = { 8 | logging = "OVERRIDE" 9 | log_configuration = { 10 | cloud_watch_log_group_name = "/aws/ecs/aws-ec2" 11 | } 12 | } 13 | } 14 | 15 | tags = { 16 | Environment = "${var.name}-${var.environment}" 17 | Project = "Pubpub-v7" 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /infrastructure/terraform/modules/v7-cluster/outputs.tf: -------------------------------------------------------------------------------- 1 | output "cluster_info" { 2 | value = { 3 | region = var.region 4 | name = var.name 5 | vpc_id = aws_vpc.main.id 6 | environment = var.environment 7 | cluster_arn = module.ecs_cluster.arn 8 | private_subnet_ids = aws_subnet.private.*.id 9 | container_security_group_ids = [aws_security_group.ecs_tasks.id] 10 | cloudwatch_log_group_name = aws_cloudwatch_log_group.ecs.name 11 | lb_listener_arn = aws_lb_listener.main.arn 12 | alb_dns_name = aws_lb.main.dns_name 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /jobs/.env.development: -------------------------------------------------------------------------------- 1 | API_KEY="super_secret_key" 2 | DATABASE_URL="postgresql://postgres:postgres@localhost:54322/postgres" 3 | PUBPUB_URL="http://localhost:3000" -------------------------------------------------------------------------------- /jobs/next.docker.config.js: -------------------------------------------------------------------------------- 1 | const baseConfig = { 2 | experimental: { 3 | instrumentationHook: true, 4 | }, 5 | }; 6 | 7 | module.exports = baseConfig; 8 | -------------------------------------------------------------------------------- /jobs/src/clients.ts: -------------------------------------------------------------------------------- 1 | import { initClient } from "@ts-rest/core"; 2 | 3 | import { api } from "contracts"; 4 | 5 | export const internalClient = initClient(api.internal, { 6 | baseUrl: `${process.env.PUBPUB_URL}`, 7 | baseHeaders: { authorization: `Bearer ${process.env.API_KEY}` }, 8 | jsonQuery: true, 9 | }); 10 | 11 | export type InternalClient = typeof internalClient; 12 | 13 | export const clients = { 14 | internalClient, 15 | } as const; 16 | 17 | export type Clients = typeof clients; 18 | export type Client = Clients[keyof Clients]; 19 | -------------------------------------------------------------------------------- /jobs/src/defineJob.ts: -------------------------------------------------------------------------------- 1 | import type { JobHelpers } from "graphile-worker"; 2 | 3 | import { logger as baseLogger } from "logger"; 4 | 5 | import type { Client } from "./clients"; 6 | 7 | type Job = ( 8 | client: C, 9 | payload: Payload, 10 | logger: typeof baseLogger, 11 | job: JobHelpers["job"] 12 | ) => Promise; 13 | 14 | export const defineJob = (job: Job) => { 15 | return (client: C) => (payload: Payload, helpers: JobHelpers) => { 16 | const jobLogger = baseLogger.child({ 17 | job: helpers.job, 18 | }); 19 | 20 | return job(client, payload, jobLogger, helpers.job); 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /jobs/src/index.ts: -------------------------------------------------------------------------------- 1 | import { run } from "graphile-worker"; 2 | 3 | import { logger } from "logger"; 4 | 5 | import { clients } from "./clients"; 6 | import { emitEvent } from "./jobs/emitEvent"; 7 | 8 | const makeTaskList = (client: typeof clients): GraphileWorker.Tasks => ({ 9 | emitEvent: emitEvent(client.internalClient), 10 | }); 11 | 12 | const main = async () => { 13 | logger.info("Starting graphile worker..."); 14 | try { 15 | const runner = await run({ 16 | connectionString: process.env.DATABASE_URL, 17 | concurrency: 5, 18 | noHandleSignals: false, 19 | pollInterval: 1000, 20 | taskList: makeTaskList(clients), 21 | }); 22 | 23 | logger.info({ msg: `Successfully started graphile worker`, runner }); 24 | await runner.promise; 25 | } catch (err) { 26 | logger.error(err); 27 | process.exit(1); 28 | } 29 | }; 30 | 31 | main(); 32 | -------------------------------------------------------------------------------- /jobs/src/tracing.ts: -------------------------------------------------------------------------------- 1 | // Example filename: tracing.js 2 | "use strict"; 3 | 4 | import { HoneycombSDK } from "@honeycombio/opentelemetry-node"; 5 | import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node"; 6 | 7 | // Uses environment variables named HONEYCOMB_API_KEY and OTEL_SERVICE_NAME 8 | const sdk = new HoneycombSDK({ 9 | instrumentations: [ 10 | getNodeAutoInstrumentations({ 11 | // We recommend disabling fs automatic instrumentation because 12 | // it can be noisy and expensive during startup 13 | "@opentelemetry/instrumentation-fs": { 14 | enabled: false, 15 | }, 16 | }), 17 | ], 18 | }); 19 | 20 | sdk.start(); 21 | -------------------------------------------------------------------------------- /jobs/src/types.ts: -------------------------------------------------------------------------------- 1 | export type InstanceJobPayload = { 2 | instanceId: string; 3 | body: T; 4 | }; 5 | -------------------------------------------------------------------------------- /jobs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", 5 | "lib": ["ESNext"], 6 | "types": ["node"], 7 | "moduleResolution": "bundler" 8 | }, 9 | "include": ["./**/*.ts"], 10 | "exclude": ["dist", "build", "node_modules", ".turbo"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/context-editor/.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-csv-to-table"; 2 | -------------------------------------------------------------------------------- /packages/context-editor/.env.development: -------------------------------------------------------------------------------- 1 | # Minio defaults to allow for file uploading in storybook. 2 | # Env vars need to be prepended by 'STORYBOOK_' for storybook to use them. 3 | 4 | STORYBOOK_ASSETS_REGION="us-east-1" 5 | STORYBOOK_ASSETS_UPLOAD_KEY="pubpubuser" 6 | STORYBOOK_ASSETS_UPLOAD_SECRET_KEY="pubpubpass" 7 | STORYBOOK_ASSETS_STORAGE_ENDPOINT="http://localhost:9000" 8 | STORYBOOK_ASSETS_BUCKET_NAME="assets.v7.pubpub.org" -------------------------------------------------------------------------------- /packages/context-editor/.env.test: -------------------------------------------------------------------------------- 1 | # Minio defaults to allow for file uploading in storybook. 2 | # Env vars need to be prepended by 'STORYBOOK_' for storybook to use them. 3 | # These should match vars in .env.docker-compose.test 4 | 5 | STORYBOOK_ASSETS_REGION="us-east-1" 6 | STORYBOOK_ASSETS_UPLOAD_KEY="pubpubuserrr" 7 | STORYBOOK_ASSETS_UPLOAD_SECRET_KEY="pubpubpass" 8 | STORYBOOK_ASSETS_STORAGE_ENDPOINT="http://localhost:9000" 9 | STORYBOOK_ASSETS_BUCKET_NAME="byron.v7.pubpub.org" -------------------------------------------------------------------------------- /packages/context-editor/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Playwright 3 | node_modules/ 4 | /test-results/ 5 | /playwright-report/ 6 | /blob-report/ 7 | /playwright/.cache/ 8 | -------------------------------------------------------------------------------- /packages/context-editor/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from "@storybook/react"; 2 | 3 | import "../src/tailwind.css"; 4 | import "../src/style.css"; 5 | 6 | const preview: Preview = { 7 | parameters: { 8 | controls: { 9 | matchers: { 10 | color: /(background|color)$/i, 11 | date: /Date$/i, 12 | }, 13 | }, 14 | }, 15 | }; 16 | 17 | export default preview; 18 | -------------------------------------------------------------------------------- /packages/context-editor/README.md: -------------------------------------------------------------------------------- 1 | # Context Editor 2 | 3 | ## Dev 4 | 5 | To run from root: 6 | 7 | ``` 8 | pnpm run --filter context-editor storybook 9 | ``` 10 | -------------------------------------------------------------------------------- /packages/context-editor/playwright/constants.ts: -------------------------------------------------------------------------------- 1 | export const BLANK_EDITOR_STORY = "/iframe.html?id=contexteditor--blank"; 2 | export const EDITOR_WITH_IMAGE_STORY = "/iframe.html?id=contexteditor--with-image"; 3 | -------------------------------------------------------------------------------- /packages/context-editor/playwright/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Page } from "@playwright/test"; 2 | 3 | import { expect } from "@playwright/test"; 4 | 5 | export const assertMenuItemActiveState = async ({ 6 | page, 7 | name, 8 | isActive, 9 | }: { 10 | page: Page; 11 | name: string; 12 | isActive: boolean; 13 | }) => { 14 | await expect(page.getByRole("button", { name })).toHaveAttribute("aria-pressed", `${isActive}`); 15 | }; 16 | 17 | export const getProsemirrorState = async (page: Page) => { 18 | const text = await page.getByTestId("prosemirror-state").textContent(); 19 | return text ? JSON.parse(text) : null; 20 | }; 21 | -------------------------------------------------------------------------------- /packages/context-editor/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | 4 | module.exports = { 5 | plugins: { 6 | "tailwindcss/nesting": {}, 7 | tailwindcss: {}, 8 | autoprefixer: {}, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/context-editor/src/commands/images.ts: -------------------------------------------------------------------------------- 1 | import { EditorState, NodeSelection } from "prosemirror-state"; 2 | 3 | export const isImageActive = (state: EditorState) => { 4 | const { node } = state.selection as NodeSelection; 5 | return node && node.type.name === "image"; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/context-editor/src/index.ts: -------------------------------------------------------------------------------- 1 | import ContextEditor from "./ContextEditor"; 2 | 3 | export * from "./ContextEditor"; 4 | export { ContextEditor }; 5 | export * from "./utils/emptyDoc"; 6 | -------------------------------------------------------------------------------- /packages/context-editor/src/plugins/code/languages.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on https://gitlab.com/emergence-engineering/prosemirror-codemirror-block/-/blob/main/src/languages.ts 3 | * 4 | * Differences: 5 | * * No LegacyLanguages 6 | * * const array instead of enum 7 | */ 8 | 9 | export const CodeBlockLanguages = [ 10 | "javascript", 11 | "html", 12 | "css", 13 | "sql", 14 | "python", 15 | "rust", 16 | "xml", 17 | "json", 18 | "markdown", 19 | "java", 20 | "cpp", 21 | ] as const; 22 | -------------------------------------------------------------------------------- /packages/context-editor/src/plugins/reactProps.ts: -------------------------------------------------------------------------------- 1 | import { Plugin, PluginKey } from "prosemirror-state"; 2 | 3 | import type { ContextEditorProps } from "../ContextEditor"; 4 | 5 | export const reactPropsKey = new PluginKey("reactProps"); 6 | export default (initialProps: ContextEditorProps) => { 7 | return new Plugin({ 8 | key: reactPropsKey, 9 | state: { 10 | init: () => initialProps, 11 | apply: (tr, prev) => tr.getMeta(reactPropsKey) || prev, 12 | }, 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/blockquote.ts: -------------------------------------------------------------------------------- 1 | import type { DOMOutputSpec, MarkSpec, NodeSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | attrs: { 5 | id: { default: null }, 6 | class: { default: null }, 7 | }, 8 | content: "block+", 9 | group: "block", 10 | selectable: false, 11 | parseDOM: [ 12 | { 13 | tag: "blockquote", 14 | getAttrs: (node) => { 15 | return { 16 | id: (node as Element).getAttribute("id"), 17 | class: (node as Element).getAttribute("class"), 18 | }; 19 | }, 20 | }, 21 | ], 22 | toDOM: (node) => { 23 | return [ 24 | "blockquote", 25 | { 26 | class: node.attrs.class, 27 | ...(node.attrs.id && { id: node.attrs.id }), 28 | }, 29 | 0, 30 | ] as DOMOutputSpec; 31 | }, 32 | } satisfies NodeSpec; 33 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/doc.ts: -------------------------------------------------------------------------------- 1 | import type { NodeSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | content: "block+", 5 | attrs: { 6 | meta: { default: {} }, 7 | }, 8 | } satisfies NodeSpec; 9 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/em.ts: -------------------------------------------------------------------------------- 1 | import type { DOMOutputSpec, MarkSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | attrs: { 5 | id: { default: null }, 6 | class: { default: null }, 7 | }, 8 | parseDOM: [ 9 | { tag: "i" }, 10 | { 11 | tag: "em", 12 | getAttrs: (node) => { 13 | return { 14 | id: (node as Element).getAttribute("id"), 15 | class: (node as Element).getAttribute("class"), 16 | }; 17 | }, 18 | }, 19 | { 20 | style: "font-style", 21 | getAttrs: (value) => value === "italic" && null, 22 | }, 23 | ], 24 | toDOM: (mark) => { 25 | return [ 26 | "em", 27 | { 28 | class: mark.attrs.class, 29 | ...(mark.attrs.id && { id: mark.attrs.id }), 30 | }, 31 | ] as DOMOutputSpec; 32 | }, 33 | } satisfies MarkSpec; 34 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/hard_break.ts: -------------------------------------------------------------------------------- 1 | import type { DOMOutputSpec, NodeSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | inline: true, 5 | group: "inline", 6 | selectable: false, 7 | parseDOM: [{ tag: "br" }], 8 | toDOM: () => { 9 | return ["br"] as DOMOutputSpec; 10 | }, 11 | } satisfies NodeSpec; 12 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/horizontal.ts: -------------------------------------------------------------------------------- 1 | import type { DOMOutputSpec, NodeSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | attrs: { 5 | id: { default: null }, 6 | class: { default: null }, 7 | }, 8 | group: "block", 9 | parseDOM: [ 10 | { 11 | tag: "hr", 12 | getAttrs: (node) => { 13 | return { 14 | id: (node as Element).getAttribute("id"), 15 | class: (node as Element).getAttribute("class"), 16 | }; 17 | }, 18 | }, 19 | ], 20 | selectable: true, 21 | toDOM: () => { 22 | return ["div", ["hr"]] as DOMOutputSpec; 23 | }, 24 | } satisfies NodeSpec; 25 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/link.ts: -------------------------------------------------------------------------------- 1 | import type { DOMOutputSpec, MarkSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | inclusive: false, 5 | attrs: { 6 | id: { default: null }, 7 | class: { default: null }, 8 | href: { default: "" }, 9 | target: { default: null }, 10 | }, 11 | parseDOM: [ 12 | { 13 | tag: "a[href]", 14 | getAttrs: (dom) => { 15 | return { 16 | id: dom.getAttribute("id"), 17 | class: dom.getAttribute("class"), 18 | href: dom.getAttribute("href"), 19 | target: dom.getAttribute("target"), 20 | }; 21 | }, 22 | }, 23 | ], 24 | toDOM: (node) => { 25 | return ["a", { ...node.attrs }] as DOMOutputSpec; 26 | }, 27 | } satisfies MarkSpec; 28 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/paragraph.ts: -------------------------------------------------------------------------------- 1 | import type { DOMOutputSpec, NodeSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | selectable: false, 5 | content: "inline*", 6 | group: "block", 7 | attrs: { 8 | id: { default: null }, 9 | class: { default: null }, 10 | }, 11 | parseDOM: [ 12 | { 13 | tag: "p", 14 | getAttrs: (node) => { 15 | return { 16 | id: (node as Element).getAttribute("id"), 17 | class: (node as Element).getAttribute("class"), 18 | }; 19 | }, 20 | }, 21 | ], 22 | toDOM: (node) => { 23 | return [ 24 | "p", 25 | { 26 | class: node.attrs.class, 27 | ...(node.attrs.id && { id: node.attrs.id }), 28 | }, 29 | 0, 30 | ] as DOMOutputSpec; 31 | }, 32 | } satisfies NodeSpec; 33 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/strike.ts: -------------------------------------------------------------------------------- 1 | import type { DOMOutputSpec, MarkSpec } from "prosemirror-model"; 2 | 3 | const getAttrs = (node: HTMLElement) => { 4 | return { 5 | id: (node as Element).getAttribute("id"), 6 | class: (node as Element).getAttribute("class"), 7 | }; 8 | }; 9 | 10 | export default { 11 | attrs: { 12 | id: { default: null }, 13 | class: { default: null }, 14 | }, 15 | parseDOM: [ 16 | { tag: "s", getAttrs }, 17 | { tag: "strike", getAttrs }, 18 | { tag: "del", getAttrs }, 19 | ], 20 | toDOM: (mark) => { 21 | return [ 22 | "s", 23 | { 24 | class: mark.attrs.class, 25 | ...(mark.attrs.id && { id: mark.attrs.id }), 26 | }, 27 | ] as DOMOutputSpec; 28 | }, 29 | } satisfies MarkSpec; 30 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/table.ts: -------------------------------------------------------------------------------- 1 | import { tableNodes } from "prosemirror-tables"; 2 | 3 | const nodes = tableNodes({ 4 | cellContent: "block+", 5 | cellAttributes: { 6 | id: { default: null }, 7 | class: { default: null }, 8 | }, 9 | tableGroup: "block", 10 | }); 11 | 12 | nodes.table.attrs = { 13 | ...nodes.table.attrs, 14 | id: { default: null }, 15 | class: { default: null }, 16 | }; 17 | 18 | export default nodes; 19 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/text.ts: -------------------------------------------------------------------------------- 1 | import type { NodeSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | inline: true, 5 | group: "inline", 6 | toDOM: (node) => { 7 | return node.text!; 8 | }, 9 | } satisfies NodeSpec; 10 | -------------------------------------------------------------------------------- /packages/context-editor/src/schemas/underline.ts: -------------------------------------------------------------------------------- 1 | import type { DOMOutputSpec, MarkSpec } from "prosemirror-model"; 2 | 3 | export default { 4 | attrs: { 5 | id: { default: null }, 6 | class: { default: null }, 7 | }, 8 | parseDOM: [ 9 | { 10 | tag: "u", 11 | getAttrs: (node) => { 12 | return { 13 | id: (node as Element).getAttribute("id"), 14 | class: (node as Element).getAttribute("class"), 15 | }; 16 | }, 17 | }, 18 | { 19 | style: "text-decoration", 20 | getAttrs: (value) => value === "underline" && null, 21 | }, 22 | ], 23 | toDOM: (mark) => { 24 | return [ 25 | "u", 26 | { 27 | class: mark.attrs.class, 28 | ...(mark.attrs.id && { id: mark.attrs.id }), 29 | }, 30 | ] as DOMOutputSpec; 31 | }, 32 | } satisfies MarkSpec; 33 | -------------------------------------------------------------------------------- /packages/context-editor/src/stories/EditorDash/JsonPanel.tsx: -------------------------------------------------------------------------------- 1 | import type { EditorState } from "prosemirror-state"; 2 | 3 | import React, { useMemo, useState } from "react"; 4 | import JsonView from "@uiw/react-json-view"; 5 | 6 | type Props = { 7 | editorState: EditorState; 8 | }; 9 | 10 | export default function JsonPanel({ editorState }: Props) { 11 | return ( 12 | <> 13 |

Doc JSON

14 |
15 | {/*
{JSON.stringify(editorState.doc.toJSON(), null, 2)}
*/} 16 | 22 |
23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/context-editor/src/stories/assets/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/packages/context-editor/src/stories/assets/demo.mp4 -------------------------------------------------------------------------------- /packages/context-editor/src/stories/assets/image0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/packages/context-editor/src/stories/assets/image0.jpg -------------------------------------------------------------------------------- /packages/context-editor/src/stories/assets/image1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/packages/context-editor/src/stories/assets/image1.jpeg -------------------------------------------------------------------------------- /packages/context-editor/src/stories/assets/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/packages/context-editor/src/stories/assets/image2.png -------------------------------------------------------------------------------- /packages/context-editor/src/stories/assets/image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/packages/context-editor/src/stories/assets/image3.jpg -------------------------------------------------------------------------------- /packages/context-editor/src/stories/assets/sounds.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pubpub/platform/5685fb536b09794c3202ce076d7275a44a2bd706/packages/context-editor/src/stories/assets/sounds.mp3 -------------------------------------------------------------------------------- /packages/context-editor/src/stories/doc.html: -------------------------------------------------------------------------------- 1 |
2 |

Example document

3 |

test

4 |
5 | 6 |
A cool image
7 |

Picture provided by the author

8 |

MIT

9 |
10 |
11 | -------------------------------------------------------------------------------- /packages/context-editor/src/stories/docWithImage.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "doc", 3 | "attrs": { 4 | "meta": {} 5 | }, 6 | "content": [ 7 | { 8 | "type": "paragraph", 9 | "attrs": { 10 | "id": null, 11 | "class": null 12 | } 13 | }, 14 | { 15 | "type": "figure", 16 | "attrs": { 17 | "id": null, 18 | "class": null 19 | }, 20 | "content": [ 21 | { 22 | "type": "image", 23 | "attrs": { 24 | "id": "", 25 | "class": null, 26 | "alt": "cat.jpeg", 27 | "src": "http://localhost:6006/image0.jpg", 28 | "linkTo": "", 29 | "caption": false, 30 | "credit": false, 31 | "license": false, 32 | "width": 100, 33 | "align": "center", 34 | "fullResolution": false 35 | } 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /packages/context-editor/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /packages/context-editor/src/utils/emptyDoc.ts: -------------------------------------------------------------------------------- 1 | export const EMPTY_DOC = { 2 | type: "doc", 3 | attrs: { 4 | meta: {}, 5 | }, 6 | content: [ 7 | { 8 | type: "paragraph", 9 | attrs: { 10 | id: null, 11 | class: null, 12 | }, 13 | }, 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /packages/context-editor/src/utils/hasChanged.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from "prosemirror-model"; 2 | 3 | import { EditorState } from "prosemirror-state"; 4 | 5 | export const docHasChanged = (initialDoc: Node, currentEditorState: EditorState) => { 6 | const hasChanged = currentEditorState.doc.content.findDiffStart(initialDoc.content) !== null; 7 | return hasChanged; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/context-editor/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./pubValues"; 2 | export * from "./hasChanged"; 3 | -------------------------------------------------------------------------------- /packages/context-editor/src/utils/marks.ts: -------------------------------------------------------------------------------- 1 | import type { Mark, Node } from "prosemirror-model"; 2 | 3 | import { isNode } from "./nodes"; 4 | 5 | export const isMark = (node: Node | Mark): node is Mark => { 6 | return !isNode(node); 7 | }; 8 | -------------------------------------------------------------------------------- /packages/context-editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "jsx": "react", 6 | "resolveJsonModule": true 7 | }, 8 | "include": [".", ".d.ts", "vite-env.d.ts"], 9 | "exclude": ["dist", "build", "node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/context-editor/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | interface ImportMeta { 2 | readonly env: Record; 3 | } 4 | -------------------------------------------------------------------------------- /packages/context-editor/vitest.config.mts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import { defineConfig } from "vitest/config"; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | environment: "jsdom", 8 | exclude: [ 9 | "**/playwright/**", 10 | "**/node_modules/**", 11 | "**/dist/**", 12 | "**/cypress/**", 13 | "**/.{idea,git,cache,output,temp}/**", 14 | "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*", 15 | ], 16 | // Clean up DOM between test runs https://testing-library.com/docs/vue-testing-library/setup/ 17 | globals: true, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /packages/contracts/README.md: -------------------------------------------------------------------------------- 1 | # @pubpub/sdk 2 | -------------------------------------------------------------------------------- /packages/contracts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { initContract } from "@ts-rest/core"; 2 | 3 | import { internalApi } from "./resources/internal"; 4 | import { siteApi } from "./resources/site"; 5 | 6 | export * from "./resources/internal"; 7 | export * from "./resources/site"; 8 | export * from "./resources/types"; 9 | 10 | const contract = initContract(); 11 | 12 | export const api = contract.router({ 13 | /** 14 | * internal API for triggering actions etc 15 | */ 16 | internal: internalApi, 17 | /** 18 | * Site builder API 19 | */ 20 | site: siteApi, 21 | }); 22 | -------------------------------------------------------------------------------- /packages/contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "jsx": "react" 6 | }, 7 | "include": ["."], 8 | "exclude": ["dist", "build", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/db/.env.development: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgresql://postgres:postgres@localhost:54322/postgres" 2 | -------------------------------------------------------------------------------- /packages/db/src/Database.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import type { PublicSchema } from "./public/PublicSchema"; 5 | 6 | export type Database = PublicSchema; 7 | -------------------------------------------------------------------------------- /packages/db/src/public/Action.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.Action */ 7 | export enum Action { 8 | log = "log", 9 | pdf = "pdf", 10 | email = "email", 11 | pushToV6 = "pushToV6", 12 | http = "http", 13 | move = "move", 14 | googleDriveImport = "googleDriveImport", 15 | datacite = "datacite", 16 | } 17 | 18 | /** Zod schema for Action */ 19 | export const actionSchema = z.nativeEnum(Action); 20 | -------------------------------------------------------------------------------- /packages/db/src/public/ActionRunStatus.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.ActionRunStatus */ 7 | export enum ActionRunStatus { 8 | success = "success", 9 | failure = "failure", 10 | scheduled = "scheduled", 11 | } 12 | 13 | /** Zod schema for ActionRunStatus */ 14 | export const actionRunStatusSchema = z.nativeEnum(ActionRunStatus); 15 | -------------------------------------------------------------------------------- /packages/db/src/public/ApiAccessScope.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.ApiAccessScope */ 7 | export enum ApiAccessScope { 8 | community = "community", 9 | pub = "pub", 10 | stage = "stage", 11 | member = "member", 12 | pubType = "pubType", 13 | } 14 | 15 | /** Zod schema for ApiAccessScope */ 16 | export const apiAccessScopeSchema = z.nativeEnum(ApiAccessScope); 17 | -------------------------------------------------------------------------------- /packages/db/src/public/ApiAccessType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.ApiAccessType */ 7 | export enum ApiAccessType { 8 | read = "read", 9 | write = "write", 10 | archive = "archive", 11 | } 12 | 13 | /** Zod schema for ApiAccessType */ 14 | export const apiAccessTypeSchema = z.nativeEnum(ApiAccessType); 15 | -------------------------------------------------------------------------------- /packages/db/src/public/AuthTokenType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** 7 | * Represents the enum public.AuthTokenType 8 | * @property generic - For most use-cases. This will just authenticate you with a regular session. 9 | * @property passwordReset - For resetting your password only 10 | * @property signup - For signing up, but also when you're invited to a community 11 | * @property verifyEmail - For verifying your email address 12 | */ 13 | export enum AuthTokenType { 14 | generic = "generic", 15 | passwordReset = "passwordReset", 16 | signup = "signup", 17 | verifyEmail = "verifyEmail", 18 | } 19 | 20 | /** Zod schema for AuthTokenType */ 21 | export const authTokenTypeSchema = z.nativeEnum(AuthTokenType); 22 | -------------------------------------------------------------------------------- /packages/db/src/public/CoreSchemaType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.CoreSchemaType */ 7 | export enum CoreSchemaType { 8 | String = "String", 9 | Boolean = "Boolean", 10 | Vector3 = "Vector3", 11 | DateTime = "DateTime", 12 | Email = "Email", 13 | URL = "URL", 14 | MemberId = "MemberId", 15 | FileUpload = "FileUpload", 16 | Null = "Null", 17 | Number = "Number", 18 | NumericArray = "NumericArray", 19 | StringArray = "StringArray", 20 | RichText = "RichText", 21 | Color = "Color", 22 | } 23 | 24 | /** Zod schema for CoreSchemaType */ 25 | export const coreSchemaTypeSchema = z.nativeEnum(CoreSchemaType); 26 | -------------------------------------------------------------------------------- /packages/db/src/public/CrudType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.CrudType */ 7 | export enum CrudType { 8 | create = "create", 9 | update = "update", 10 | "delete" = "delete", 11 | } 12 | 13 | /** Zod schema for CrudType */ 14 | export const crudTypeSchema = z.nativeEnum(CrudType); 15 | -------------------------------------------------------------------------------- /packages/db/src/public/ElementType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.ElementType */ 7 | export enum ElementType { 8 | pubfield = "pubfield", 9 | structural = "structural", 10 | button = "button", 11 | } 12 | 13 | /** Zod schema for ElementType */ 14 | export const elementTypeSchema = z.nativeEnum(ElementType); 15 | -------------------------------------------------------------------------------- /packages/db/src/public/Event.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.Event */ 7 | export enum Event { 8 | pubEnteredStage = "pubEnteredStage", 9 | pubLeftStage = "pubLeftStage", 10 | pubInStageForDuration = "pubInStageForDuration", 11 | actionSucceeded = "actionSucceeded", 12 | actionFailed = "actionFailed", 13 | } 14 | 15 | /** Zod schema for Event */ 16 | export const eventSchema = z.nativeEnum(Event); 17 | -------------------------------------------------------------------------------- /packages/db/src/public/FormAccessType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.FormAccessType */ 7 | export enum FormAccessType { 8 | private = "private", 9 | public = "public", 10 | } 11 | 12 | /** Zod schema for FormAccessType */ 13 | export const formAccessTypeSchema = z.nativeEnum(FormAccessType); 14 | -------------------------------------------------------------------------------- /packages/db/src/public/InputComponent.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.InputComponent */ 7 | export enum InputComponent { 8 | textArea = "textArea", 9 | textInput = "textInput", 10 | datePicker = "datePicker", 11 | checkbox = "checkbox", 12 | fileUpload = "fileUpload", 13 | memberSelect = "memberSelect", 14 | confidenceInterval = "confidenceInterval", 15 | checkboxGroup = "checkboxGroup", 16 | radioGroup = "radioGroup", 17 | selectDropdown = "selectDropdown", 18 | multivalueInput = "multivalueInput", 19 | richText = "richText", 20 | relationBlock = "relationBlock", 21 | colorPicker = "colorPicker", 22 | } 23 | 24 | /** Zod schema for InputComponent */ 25 | export const inputComponentSchema = z.nativeEnum(InputComponent); 26 | -------------------------------------------------------------------------------- /packages/db/src/public/MemberRole.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.MemberRole */ 7 | export enum MemberRole { 8 | admin = "admin", 9 | editor = "editor", 10 | contributor = "contributor", 11 | } 12 | 13 | /** Zod schema for MemberRole */ 14 | export const memberRoleSchema = z.nativeEnum(MemberRole); 15 | -------------------------------------------------------------------------------- /packages/db/src/public/MembershipType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.MembershipType */ 7 | export enum MembershipType { 8 | community = "community", 9 | stage = "stage", 10 | pub = "pub", 11 | } 12 | 13 | /** Zod schema for MembershipType */ 14 | export const membershipTypeSchema = z.nativeEnum(MembershipType); 15 | -------------------------------------------------------------------------------- /packages/db/src/public/ModifiedByType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the domain public.modified_by_type */ 7 | export type ModifiedByType = string; 8 | 9 | /** Zod schema for modified_by_type */ 10 | export const modifiedByTypeSchema = z.string(); 11 | -------------------------------------------------------------------------------- /packages/db/src/public/OperationType.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.OperationType */ 7 | export enum OperationType { 8 | insert = "insert", 9 | update = "update", 10 | "delete" = "delete", 11 | } 12 | 13 | /** Zod schema for OperationType */ 14 | export const operationTypeSchema = z.nativeEnum(OperationType); 15 | -------------------------------------------------------------------------------- /packages/db/src/public/StructuralFormElement.ts: -------------------------------------------------------------------------------- 1 | // @generated 2 | // This file is automatically generated by Kanel. Do not modify manually. 3 | 4 | import { z } from "zod"; 5 | 6 | /** Represents the enum public.StructuralFormElement */ 7 | export enum StructuralFormElement { 8 | h2 = "h2", 9 | h3 = "h3", 10 | p = "p", 11 | hr = "hr", 12 | } 13 | 14 | /** Zod schema for StructuralFormElement */ 15 | export const structuralFormElementSchema = z.nativeEnum(StructuralFormElement); 16 | -------------------------------------------------------------------------------- /packages/db/src/types/LastModifiedBy.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | import { uuidRegex } from "utils/uuid"; 4 | 5 | import type { ActionRunsId, ApiAccessTokensId, UsersId } from "../public"; 6 | 7 | export type LastModifiedBy = `${ 8 | | `user:${UsersId}` 9 | | `action-run:${ActionRunsId}` 10 | | `api-access-token:${ApiAccessTokensId}` 11 | | "unknown" 12 | | "system"}|${number}`; 13 | 14 | const regex = `^((user|action-run|api-access-token):${uuidRegex.source.replace(/\^|\$/g, "")}|(?:system|unknown))\|\d{13}$`; 15 | 16 | export const lastModifiedBySchema = z 17 | .string() 18 | .regex(new RegExp(regex)) as z.ZodType; 19 | -------------------------------------------------------------------------------- /packages/db/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ApiAccessToken"; 2 | export * from "./LastModifiedBy"; 3 | export * from "./HistoryTable"; 4 | export * from "./Invite"; 5 | -------------------------------------------------------------------------------- /packages/db/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "isolatedModules": true, 6 | "moduleResolution": "bundler" 7 | }, 8 | "include": ["./src", "scripts/comment-generator.ts"], 9 | "exclude": ["dist", "build", "node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/emails/.env.development: -------------------------------------------------------------------------------- 1 | PUBPUB_URL=http://localhost:3000 2 | -------------------------------------------------------------------------------- /packages/emails/emails/static/a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /packages/emails/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import baseConfig from "@pubpub/eslint-config/base"; 4 | import reactConfig from "@pubpub/eslint-config/react"; 5 | 6 | /** @type {import('typescript-eslint').Config} */ 7 | export default [ 8 | { 9 | ignores: ["dist/**"], 10 | }, 11 | ...baseConfig, 12 | ...reactConfig, 13 | ]; 14 | -------------------------------------------------------------------------------- /packages/emails/src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./password-reset"; 2 | export * from "./signup-invite"; 3 | export * from "./request-link-to-form"; 4 | export * from "./verify-email"; 5 | -------------------------------------------------------------------------------- /packages/emails/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "include": ["./**/*.ts*"], 4 | "exclude": ["dist", "build", "node_modules"], 5 | "compilerOptions": { 6 | "strict": true, 7 | "jsx": "react-jsx", 8 | "moduleResolution": "bundler" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/logger/src/base.ts: -------------------------------------------------------------------------------- 1 | import { pino } from "pino"; 2 | 3 | export const logger = pino({ 4 | level: process.env.LOG_LEVEL || "info", 5 | customLevels: { 6 | benchmark: 15, 7 | }, 8 | base: { 9 | // this will add what package/app is logging the current statement to every log 10 | // might be useful once we choose to colate logs from multiple apps through some kind of log aggregator 11 | package: process.env.npm_package_name, 12 | }, 13 | }); 14 | 15 | export const benchmark = 16 | (name: string) => 17 | async (thing: () => Promise) => { 18 | if (!logger.isLevelEnabled("benchmark")) { 19 | return await thing(); 20 | } 21 | 22 | const start = performance.now(); 23 | const result = await thing(); 24 | const duration = performance.now() - start; 25 | logger.benchmark({ msg: name, duration }); 26 | 27 | return result; 28 | }; 29 | -------------------------------------------------------------------------------- /packages/logger/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./base"; 2 | -------------------------------------------------------------------------------- /packages/logger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/base.json", 3 | "compilerOptions": { 4 | "noEmit": true 5 | }, 6 | "include": ["."], 7 | "exclude": ["dist", "build", "node_modules"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/schemas/src/types.ts: -------------------------------------------------------------------------------- 1 | export enum MinMaxChoices { 2 | AtLeast = "At Least", 3 | AtMost = "At Most", 4 | Exactly = "Exactly", 5 | } 6 | -------------------------------------------------------------------------------- /packages/schemas/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/react-library.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json" 6 | }, 7 | "include": ["."], 8 | "exclude": ["dist", "build", "node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "iconLibrary": "lucide", 7 | "tailwind": { 8 | "config": "tailwind.config.cjs", 9 | "css": "./styles.css", 10 | "baseColor": "gray", 11 | "cssVariables": false 12 | }, 13 | "aliases": { 14 | "components": "src", 15 | "utils": "utils", 16 | "ui": "src" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/ui/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import baseConfig from "@pubpub/eslint-config/base"; 4 | import reactConfig from "@pubpub/eslint-config/react"; 5 | 6 | /** @type {import('typescript-eslint').Config} */ 7 | export default [ 8 | { 9 | ignores: ["dist/**"], 10 | languageOptions: { 11 | parserOptions: { 12 | projectService: true, 13 | tsconfigRootDir: import.meta.dirname, 14 | }, 15 | }, 16 | }, 17 | ...baseConfig, 18 | ...reactConfig, 19 | ]; 20 | -------------------------------------------------------------------------------- /packages/ui/hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined); 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 12 | }; 13 | mql.addEventListener("change", onChange); 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 15 | return () => mql.removeEventListener("change", onChange); 16 | }, []); 17 | 18 | return !!isMobile; 19 | } 20 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | 4 | module.exports = { 5 | plugins: { 6 | tailwindcss: {}, 7 | autoprefixer: {}, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/ui/src/actionInstances/ActionInstanceSelectorInput.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import type { AutoFormInputComponentProps } from "../auto-form"; 4 | import { ActionInstanceSelector } from "./ActionInstanceSelector"; 5 | 6 | export const ActionInstanceSelectorInput = (props: AutoFormInputComponentProps) => { 7 | return ; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/ui/src/actionInstances/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ActionInstancesContext"; 2 | export * from "./ActionInstanceSelector"; 3 | -------------------------------------------------------------------------------- /packages/ui/src/auto-form/common/description.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "utils"; 4 | 5 | import { FormDescription } from "../../form"; 6 | 7 | function AutoFormDescription({ 8 | description, 9 | className, 10 | }: { 11 | description: string; 12 | className?: string; 13 | }) { 14 | return ( 15 | <> 16 | {description} 17 | 18 | ); 19 | } 20 | 21 | export default AutoFormDescription; 22 | -------------------------------------------------------------------------------- /packages/ui/src/auto-form/common/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "utils"; 4 | 5 | import { FormLabel } from "../../form"; 6 | 7 | function AutoFormLabel({ 8 | label, 9 | isRequired, 10 | className, 11 | id, 12 | }: { 13 | label: string; 14 | isRequired: boolean; 15 | className?: string; 16 | id?: string; 17 | }) { 18 | return ( 19 | <> 20 | 21 | {label} 22 | {isRequired && *} 23 | 24 | 25 | ); 26 | } 27 | 28 | export default AutoFormLabel; 29 | -------------------------------------------------------------------------------- /packages/ui/src/auto-form/common/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | function AutoFormTooltip({ fieldConfigItem }: { fieldConfigItem: any }) { 4 | return ( 5 | <> 6 | {fieldConfigItem?.description && ( 7 |

8 | {fieldConfigItem.description} 9 |

10 | )} 11 | 12 | ); 13 | } 14 | 15 | export default AutoFormTooltip; 16 | -------------------------------------------------------------------------------- /packages/ui/src/auto-form/dependencyType.ts: -------------------------------------------------------------------------------- 1 | export enum DependencyType { 2 | DISABLES, 3 | REQUIRES, 4 | HIDES, 5 | SETS_OPTIONS, 6 | } 7 | -------------------------------------------------------------------------------- /packages/ui/src/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; 4 | 5 | const Collapsible = CollapsiblePrimitive.Root; 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 12 | -------------------------------------------------------------------------------- /packages/ui/src/customRenderers/confidence/confidence.css: -------------------------------------------------------------------------------- 1 | .slider-thumb:after { 2 | position: absolute; 3 | top: 1.5rem; 4 | content: attr(aria-valuenow); 5 | font-size: 0.8rem; 6 | } 7 | -------------------------------------------------------------------------------- /packages/ui/src/data-table-paged/hooks/use-callback-ref.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | /** 4 | * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx 5 | */ 6 | 7 | /** 8 | * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a 9 | * prop or avoid re-executing effects when passed as a dependency 10 | */ 11 | function useCallbackRef unknown>(callback: T | undefined): T { 12 | const callbackRef = React.useRef(callback); 13 | 14 | React.useEffect(() => { 15 | callbackRef.current = callback; 16 | }); 17 | 18 | // https://github.com/facebook/react/issues/19240 19 | return React.useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []); 20 | } 21 | 22 | export { useCallbackRef }; 23 | -------------------------------------------------------------------------------- /packages/ui/src/data-table-paged/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | export * from "./components/data-table"; 3 | export * from "./components/data-table-pagination"; 4 | export * from "./config/data-table"; 5 | export * from "./hooks/use-data-table"; 6 | export * from "./lib/parsers"; 7 | -------------------------------------------------------------------------------- /packages/ui/src/editors/SingleLinePlugin.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; 3 | import { mergeRegister } from "@lexical/utils"; 4 | import { LineBreakNode, RootNode } from "lexical"; 5 | 6 | // From https://github.com/facebook/lexical/issues/3675 7 | export const SingleLinePlugin = () => { 8 | const [editor] = useLexicalComposerContext(); 9 | useEffect( 10 | () => 11 | mergeRegister( 12 | editor.registerNodeTransform(RootNode, (rootNode: RootNode) => { 13 | if (rootNode.getChildrenSize() <= 1) return; 14 | rootNode.getLastChild()?.remove(); 15 | }), 16 | editor.registerNodeTransform(LineBreakNode, (node) => { 17 | node.remove(); 18 | }) 19 | ), 20 | [editor] 21 | ); 22 | return null; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ui/src/editors/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import type { AutoFormInputComponentProps } from "../auto-form"; 4 | import { LexicalEditor } from "./LexicalEditor"; 5 | 6 | export const MarkdownEditor = (props: AutoFormInputComponentProps) => { 7 | return ; 8 | }; 9 | 10 | export const InputWithTokens = (props: AutoFormInputComponentProps) => { 11 | return ; 12 | }; 13 | 14 | export { LexicalEditor } from "./LexicalEditor"; 15 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./useLocalStorage"; 2 | export * from "./useUnsavedChangesWarning"; 3 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/useMobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | const MOBILE_BREAKPOINT = 768; 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined); 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 12 | }; 13 | mql.addEventListener("change", onChange); 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 15 | return () => mql.removeEventListener("change", onChange); 16 | }, []); 17 | 18 | return !!isMobile; 19 | } 20 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/useUnsavedChangesWarning.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | 5 | export const useUnsavedChangesWarning = (enabled = true) => { 6 | React.useEffect(() => { 7 | const handleBeforeUnload = (event: BeforeUnloadEvent) => { 8 | if (enabled) { 9 | // The preventDefault call is necessary to trigger the browser's 10 | // confirmation dialog. 11 | event.preventDefault(); 12 | } 13 | }; 14 | window.addEventListener("beforeunload", handleBeforeUnload); 15 | return () => { 16 | window.removeEventListener("beforeunload", handleBeforeUnload); 17 | }; 18 | }, [enabled]); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ui/src/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import type { VariantProps } from "class-variance-authority"; 4 | 5 | import * as React from "react"; 6 | import * as LabelPrimitive from "@radix-ui/react-label"; 7 | import { cva } from "class-variance-authority"; 8 | 9 | import { cn } from "utils"; 10 | 11 | const labelVariants = cva( 12 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 13 | ); 14 | 15 | const Label = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef & VariantProps 18 | >(({ className, ...props }, ref) => ( 19 | 20 | )); 21 | Label.displayName = LabelPrimitive.Root.displayName; 22 | 23 | export { Label }; 24 | -------------------------------------------------------------------------------- /packages/ui/src/pubFields/PubFieldContext.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { createContext, useContext } from "react"; 4 | 5 | import type { PubFields, PubFieldsId } from "db/public"; 6 | 7 | export type PubField = Pick< 8 | PubFields, 9 | "id" | "name" | "slug" | "schemaName" | "pubFieldSchemaId" | "isArchived" | "isRelation" 10 | >; 11 | export type PubFieldContext = Record; 12 | 13 | type Props = { 14 | children: React.ReactNode; 15 | pubFields: PubFieldContext; 16 | }; 17 | 18 | const PubFieldContext = createContext({}); 19 | 20 | export function PubFieldProvider({ children, pubFields }: Props) { 21 | return {children}; 22 | } 23 | 24 | export const usePubFieldContext = () => useContext(PubFieldContext); 25 | -------------------------------------------------------------------------------- /packages/ui/src/pubFields/index.tsx: -------------------------------------------------------------------------------- 1 | export { type PubFieldContext, PubFieldProvider, usePubFieldContext } from "./PubFieldContext"; 2 | 3 | export { 4 | PubFieldSelect as PubFieldSelector, 5 | PubFieldSelectWrapper as PubFieldSelectorHider, 6 | PubFieldSelectProvider as PubFieldSelectorProvider, 7 | PubFieldSelectToggleButton as PubFieldSelectorToggleButton, 8 | } from "./pubFieldSelect"; 9 | -------------------------------------------------------------------------------- /packages/ui/src/pubFields/pubFieldSelect/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | PubFieldSelect, 3 | PubFieldSelectWrapper, 4 | PubFieldSelectProvider, 5 | PubFieldSelectToggleButton, 6 | } from "./PubFieldSelect"; 7 | -------------------------------------------------------------------------------- /packages/ui/src/pubTypes/PubTypesContext.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { createContext, useContext } from "react"; 4 | 5 | import type { PubTypes } from "db/public"; 6 | 7 | type Props = { 8 | children: React.ReactNode; 9 | pubTypes: PubTypes[]; 10 | }; 11 | 12 | const PubTypeContext = createContext([]); 13 | 14 | export function PubTypeProvider({ children, pubTypes }: Props) { 15 | return {children}; 16 | } 17 | 18 | export const usePubTypeContext = () => useContext(PubTypeContext); 19 | -------------------------------------------------------------------------------- /packages/ui/src/pubTypes/index.tsx: -------------------------------------------------------------------------------- 1 | export { PubTypeProvider, usePubTypeContext } from "./PubTypesContext"; 2 | -------------------------------------------------------------------------------- /packages/ui/src/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 5 | 6 | import { cn } from "utils"; 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => ( 12 | 23 | )); 24 | Separator.displayName = SeparatorPrimitive.Root.displayName; 25 | 26 | export { Separator }; 27 | -------------------------------------------------------------------------------- /packages/ui/src/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "utils"; 4 | 5 | function Skeleton({ className, ...props }: React.HTMLAttributes) { 6 | return ( 7 |
11 | ); 12 | } 13 | 14 | export { Skeleton }; 15 | -------------------------------------------------------------------------------- /packages/ui/src/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "utils"; 4 | 5 | export interface TextareaProps extends React.TextareaHTMLAttributes {} 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 |