Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(self-host|3): self-hostable platform! #982

Merged
merged 41 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6ba77ea
feat: allow local file uploads with minio
tefkah Jan 27, 2025
60758c9
feat: allow different email providers to be used when self-hosting, o…
tefkah Feb 12, 2025
c3bee15
fix: handle errors and fix types
tefkah Feb 12, 2025
745ed9f
chore: merge
tefkah Feb 12, 2025
8b17fd8
fix: make minio slightly easier to start
tefkah Feb 12, 2025
c25e50a
fix: add upload test
tefkah Feb 12, 2025
fad6bd4
chore: format
tefkah Feb 17, 2025
d57d64a
chore: merge
tefkah Feb 17, 2025
d43f265
chore: format?
tefkah Feb 17, 2025
2614c9d
fix: get docker compose stuff in reasonable state
tefkah Feb 18, 2025
3250fc2
fix: hopefully make ci run correctly
tefkah Feb 18, 2025
32c2050
fix: env vars?
tefkah Feb 18, 2025
7db5920
fix: unfix env vars
tefkah Feb 18, 2025
a4393ff
fix: remove unecessary option
tefkah Feb 18, 2025
408e68a
fix: shuffle things around more
tefkah Feb 18, 2025
e2bb652
fix: add manual database_url back
tefkah Feb 18, 2025
e8770ca
fix: don't use non-existint postgres_name var, use postgres_db
tefkah Feb 18, 2025
9c7477f
fix: log on cancel, and max fail 3 tests
tefkah Feb 18, 2025
51b0b38
fix: change db url
tefkah Feb 18, 2025
7fb5233
fix: actually pass in correct env vars this time??
tefkah Feb 18, 2025
c3b0357
fix: change a bit how i'm doing env vars (shocking)
tefkah Feb 18, 2025
8b2f9e4
fix: don't use env.docker-compose in ci directly
tefkah Feb 18, 2025
29d413f
fix: okay i have learned how env vars work. you cannot set 'environme…
tefkah Feb 18, 2025
1fe827b
fix: minio_root_user instead
tefkah Feb 18, 2025
2385cab
chore: restore dirty
tefkah Feb 19, 2025
bd4b834
docs: update self-host docs
tefkah Feb 19, 2025
84f73c9
chore: clean up test file
tefkah Feb 19, 2025
a8ea108
chore: remove file staged for later commit
tefkah Feb 19, 2025
3315a1c
feat: add docker-compose file for self hosting
tefkah Feb 19, 2025
4bd6154
feat: add caddy file config and all that jazz
tefkah Feb 19, 2025
c1f3351
feat: add way to add admin user
tefkah Feb 19, 2025
9961154
docs: update environment variable docs
tefkah Feb 19, 2025
503022f
docs: provide better documentation
tefkah Feb 19, 2025
6c7c53b
chore: remove console.log
tefkah Feb 19, 2025
3ebc6e0
fix: better env vars for db
tefkah Feb 19, 2025
6d46157
chore: add explanatory comment
tefkah Feb 20, 2025
60cce53
Merge branch 'tfk/minio' into tfk/self-host-setup
tefkah Feb 20, 2025
df89acd
chore: merge
tefkah Feb 27, 2025
e2f921c
fix: improve order of .env.example a bit
tefkah Feb 27, 2025
486c65a
chore: do not log out password after account creation
tefkah Feb 27, 2025
cd840da
chore: start minio for test again (oops)
tefkah Feb 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .env.docker-compose.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
MINIO_ROOT_USER=pubpub-minio-admin
MINIO_ROOT_PASSWORD=pubpub-minio-admin

ASSETS_BUCKET_NAME=assets.v7.pubpub.org
ASSETS_UPLOAD_KEY=pubpubuser
ASSETS_UPLOAD_SECRET_KEY=pubpubpass
ASSETS_REGION=us-east-1

POSTGRES_PORT=54322
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=postgres

40 changes: 40 additions & 0 deletions .env.docker-compose.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
MINIO_ROOT_USER=pubpub-minio-admin
MINIO_ROOT_PASSWORD=pubpub-minio-admin

ASSETS_BUCKET_NAME=byron.v7.pubpub.org
ASSETS_UPLOAD_KEY=pubpubuserrr
ASSETS_UPLOAD_SECRET_KEY=pubpubpass
ASSETS_REGION=us-east-1
ASSETS_STORAGE_ENDPOINT=http://localhost:9000

POSTGRES_PORT=54323
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=postgres
POSTGRES_HOST=db

# annoying duplication because jobs uses this version
PGHOST=db
PGPORT=5432
PGUSER=postgres
PGPASSWORD=postgres
PGDATABASE=postgres

# this needs to be db:5432 bc that's what it is in the app-network
# if you are running this from outside the docker network, you need to use
# @localhost:${POSTGRES_PORT} instead
DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres


JWT_SECRET=xxx
MAILGUN_SMTP_PASSWORD=xxx
GCLOUD_KEY_FILE=xxx

MAILGUN_SMTP_HOST=inbucket
MAILGUN_SMTP_PORT=2500
# this needs to be localhost:54324 instead of inbucket:9000 bc we are almost always running the integration tests from outside the docker network
INBUCKET_URL=http://localhost:54324
MAILGUN_SMTP_USERNAME=omitted
OTEL_SERVICE_NAME=core.core
PUBPUB_URL=http://localhost:3000
API_KEY=xxx
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
runs-on: ubuntu-latest
env:
COMPOSE_FILE: docker-compose.test.yml
ENV_FILE: .env.docker-compose.test
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -55,8 +56,8 @@ jobs:
restore-keys: |
${{ runner.os }}-turbo-

- name: Start up DB
run: docker compose --profile test up -d
- name: Start test dependencies
run: pnpm test:setup

- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline
Expand All @@ -66,8 +67,6 @@ jobs:

- name: Run migrations
run: pnpm --filter core migrate-test
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5433/postgres

- name: Run prettier
run: pnpm format
Expand Down
46 changes: 21 additions & 25 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
integration-tests:
name: Integration tests
runs-on: ubuntu-latest
env:
ENV_FILE: .env.docker-compose.test
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down Expand Up @@ -48,28 +50,6 @@ jobs:
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline

- name: Start up DB
run: docker compose -f docker-compose.test.yml --profile test up -d

- name: p:build
run: pnpm p:build

- name: Run migrations
run: pnpm --filter core prisma migrate deploy
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5433/postgres

- name: seed db
run: pnpm --filter core prisma db seed
env:
# 20241126: this prevents the arcadia seed from running, which contains a ton of pubs which potentially might slow down the tests
MINIMAL_SEED: true
SKIP_VALIDATION: true
DATABASE_URL: postgresql://postgres:postgres@localhost:5433/postgres

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
Expand Down Expand Up @@ -97,10 +77,26 @@ jobs:
echo "jobs_label=$ECR_REGISTRY/${ECR_REPOSITORY_NAME_OVERRIDE:-$ECR_REPOSITORY_PREFIX-jobs}:$IMAGE_TAG" >> $GITHUB_OUTPUT
echo "base_label=$ECR_REGISTRY/$ECR_REPOSITORY_PREFIX:$IMAGE_TAG" >> $GITHUB_OUTPUT

- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline

- name: Start up db images
run: pnpm test:setup

- name: p:build
run: pnpm p:build

- name: Run migrations and seed
run: pnpm --filter core db:test:reset
env:
# 20241126: this prevents the arcadia seed from running, which contains a ton of pubs which potentially might slow down the tests
MINIMAL_SEED: true
SKIP_VALIDATION: true

- run: pnpm --filter core exec playwright install chromium --with-deps

- name: Start up core
run: docker compose -f docker-compose.test.yml --profile integration up -d
- name: Start up core etc
run: pnpm integration:setup
env:
INTEGRATION_TESTS_IMAGE: ${{steps.label.outputs.core_label}}
JOBS_IMAGE: ${{steps.label.outputs.jobs_label}}
Expand All @@ -118,7 +114,7 @@ jobs:
INTEGRATION_TEST_HOST: localhost

- name: Print container logs
if: failure()
if: ${{failure() || cancelled()}}
run: docker compose -f docker-compose.test.yml --profile integration logs

- name: Upload playwright snapshots artifact
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,6 @@ core/supabase/.temp
*storybook.log
storybook-static

./playwright
./playwright

.local_data
7 changes: 5 additions & 2 deletions core/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ PUBPUB_URL="http://localhost:3000"
ASSETS_BUCKET_NAME="assets.v7.pubpub.org"
ASSETS_REGION="us-east-1"

ASSETS_UPLOAD_KEY="xxx"
ASSETS_UPLOAD_SECRET_KEY="xxx"
# mninio defaults
ASSETS_UPLOAD_KEY="pubpubuser"
ASSETS_UPLOAD_SECRET_KEY="pubpubpass"
ASSETS_STORAGE_ENDPOINT="http://localhost:9000"

MAILGUN_SMTP_PASSWORD="xxx"
MAILGUN_SMTP_USERNAME="xxx"

Expand Down
13 changes: 8 additions & 5 deletions core/.env.test
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
DATABASE_URL=postgresql://postgres:postgres@localhost:5433/postgres
DATABASE_URL=postgresql://postgres:postgres@localhost:54323/postgres
PUBPUB_URL=http://localhost:3000
MAILGUN_SMTP_HOST=localhost
MAILGUN_SMTP_PORT=54325
API_KEY="super_secret_key"
ASSETS_BUCKET_NAME="assets.v7.pubpub.org"
ASSETS_REGION="us-east-1"

ASSETS_UPLOAD_KEY="xxx"
ASSETS_UPLOAD_SECRET_KEY="xxx"
ASSETS_BUCKET_NAME=byron.v7.pubpub.org
ASSETS_UPLOAD_KEY=pubpubuserrr
ASSETS_UPLOAD_SECRET_KEY=pubpubpass
ASSETS_REGION=us-east-1
ASSETS_STORAGE_ENDPOINT="http://localhost:9000"

MAILGUN_SMTP_PASSWORD="xxx"
MAILGUN_SMTP_USERNAME="xxx"

Expand All @@ -17,3 +19,4 @@ HONEYCOMB_API_KEY="xxx"
KYSELY_DEBUG="true"

GCLOUD_KEY_FILE='xxx'

2 changes: 1 addition & 1 deletion core/app/components/forms/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function FileUploadPreview({ files }: { files: FileUpload }) {
The file is <strong>{file.fileSize}</strong> bytes in size. Its
MIME type is <strong>{file.fileType}</strong>.
</p>
<Button variant="secondary">
<Button variant="secondary" asChild>
<a target="_blank" href={file.fileUploadUrl}>
Open file in new tab
</a>
Expand Down
5 changes: 3 additions & 2 deletions core/app/components/forms/elements/FileUploadElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export const FileUploadElement = ({
const signedUploadUrl = (fileName: string) => {
return upload(pubId, fileName);
};
const { control, getValues } = useFormContext();
const { control, getValues, formState } = useFormContext();

const formElementToggle = useFormElementToggleContext();
const isEnabled = formElementToggle.isEnabled(slug);
const files = getValues()[slug];
Expand All @@ -58,7 +59,7 @@ export const FileUploadElement = ({
{...field}
disabled={!isEnabled}
upload={signedUploadUrl}
onUpdateFiles={(event: any[]) => {
onUpdateFiles={(event) => {
field.onChange(event);
}}
id={slug}
Expand Down
1 change: 1 addition & 0 deletions core/lib/env/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const env = createEnv({
ASSETS_REGION: z.string(),
ASSETS_UPLOAD_KEY: z.string(),
ASSETS_UPLOAD_SECRET_KEY: z.string(),
ASSETS_STORAGE_ENDPOINT: z.string().url().optional(),
/**
* Whether or not to verbosely log `memoize` cache hits and misses
*/
Expand Down
2 changes: 2 additions & 0 deletions core/lib/server/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ export const generateSignedAssetUploadUrl = async (pubId: PubsId, fileName: stri
const bucket = env.ASSETS_BUCKET_NAME;

const client = new S3Client({
endpoint: env.ASSETS_STORAGE_ENDPOINT,
region: region,
credentials: {
accessKeyId: key,
secretAccessKey: secret,
},
forcePathStyle: !!env.ASSETS_STORAGE_ENDPOINT, // Required for MinIO
});
const command = new PutObjectCommand({
Bucket: bucket,
Expand Down
3 changes: 2 additions & 1 deletion core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"db:migrate-deploy": "pnpm migrate-deploy",
"db:migrate-diff": "pnpm migrate-diff",
"db:migrate-docker": "pnpm migrate-docker",
"db:migrate-test": "pnpm migrate-test",
"db:test:migrate": "pnpm migrate-test",
"db:test:reset": "dotenv -e .env.test -- prisma migrate reset --preview-feature --force | pino-pretty",
"db:prisma": "pnpm prisma",
"db:studio": "pnpm prisma studio",
"db:generate-history-table": "pnpm exec tsx prisma/scripts/history-tables/generate-history-table.mts",
Expand Down
2 changes: 2 additions & 0 deletions core/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export default defineConfig({
expect: {
timeout: process.env.CI ? 5_000 : 60_000,
},
// don't continue going after 3 tests have failed, that's too many
maxFailures: process.env.CI ? 3 : undefined,
// a
webServer: [
{
Expand Down
117 changes: 117 additions & 0 deletions core/playwright/fileUpload.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type { Page } from "@playwright/test";

import { expect, test } from "@playwright/test";

import { FieldsPage } from "./fixtures/fields-page";
import { LoginPage } from "./fixtures/login-page";
import { PubTypesPage } from "./fixtures/pub-types-page";
import { PubsPage } from "./fixtures/pubs-page";
import { createCommunity } from "./helpers";

const now = new Date().getTime();
const COMMUNITY_SLUG = `playwright-test-community-${now}`;

test.describe.configure({ mode: "serial" });

let page: Page;

test.beforeAll(async ({ browser }) => {
page = await browser.newPage();

const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.loginAndWaitForNavigation();

await createCommunity({
page,
community: { name: `test community ${now}`, slug: COMMUNITY_SLUG },
});

/**
* Fill out everything required to make an external form:
* 1. Fields
* 2. Form with fields
* 3. A pub
*/
// Populate the fields page with options
const fieldsPage = new FieldsPage(page, COMMUNITY_SLUG);
await fieldsPage.goto();
await fieldsPage.addFieldsOfEachType();

const pubTypePage = new PubTypesPage(page, COMMUNITY_SLUG);
await pubTypePage.goto();
await pubTypePage.addType("File Upload Test", "", ["title", "fileupload"]);
});

test.afterAll(async () => {
await page.close();
});

test.describe("File upload", () => {
test("should upload a file", async ({ context }) => {
const pubsPage = new PubsPage(page, COMMUNITY_SLUG);
await pubsPage.goTo();
const pubId = await pubsPage.createPub({
pubType: "File Upload Test",
values: { title: "The Activity of Slugs" },
});

const pubEditUrl = `/c/${COMMUNITY_SLUG}/pubs/${pubId}/edit`;
await page.goto(pubEditUrl);

await page.setInputFiles("input[type='file']", [
new URL("fixtures/test-assets/test-diagram.png", import.meta.url).pathname,
]);

await page.getByRole("button", { name: "Upload 1 file", exact: true }).click({
timeout: 2_000,
});

await page.getByText("Complete", { exact: true }).waitFor({
timeout: 10_000,
});

await page.getByRole("button", { name: "Save" }).click();
await page.getByText("Pub successfully updated", { exact: true }).waitFor({
timeout: 2_000,
});

await page.getByRole("link", { name: "View Pub", exact: true }).click();
await page.waitForURL(`/c/${COMMUNITY_SLUG}/pubs/${pubId}`);

const fileUploadValue = await page
.getByTestId(`FileUpload-value`)
.getByRole("button")
.click({
timeout: 1_000,
});

const describeThing = await page.getByText("Its MIME type is").first().textContent({
timeout: 2_000,
});

/**
* So we know the actual file is being uploaded
*/
const parsedDescription = describeThing?.match(
/The file is (\d+) bytes in size. Its MIME type is (\w+\/\w+)./
);
expect(parsedDescription).not.toBeNull();

const fileSize = parsedDescription?.[1];
const mimeType = parsedDescription?.[2];

expect(fileSize).toBeDefined();
expect(parseInt(fileSize!)).toBe(108_839);
expect(mimeType).toBe("image/png");

const link = page.getByRole("link", { name: "Open file in new tab" });

const url = await link.getAttribute("href", { timeout: 1_000 });

expect(url).toBeDefined();
await page.goto(url!);

await page.waitForURL(/localhost:9000/);
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading