diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 49cbbad44..516f8900e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,8 +3,8 @@ on: push: branches: - main - - dev - pull_request: {} + pull_request: + types: [opened, reopened, synchronize, closed] concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -14,6 +14,10 @@ permissions: actions: write contents: read +env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + # Change this if you want to deploy to a different org + FLY_ORG: personal jobs: lint: name: ⬣ ESLint @@ -146,8 +150,7 @@ jobs: container: name: πŸ“¦ Prepare Container runs-on: ubuntu-24.04 - # only prepare container on pushes - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 @@ -164,20 +167,7 @@ jobs: - name: 🎈 Setup Fly uses: superfly/flyctl-actions/setup-flyctl@1.5 - - name: πŸ“¦ Build Staging Container - if: ${{ github.ref == 'refs/heads/dev' }} - run: | - flyctl deploy \ - --build-only \ - --push \ - --image-label ${{ github.sha }} \ - --build-arg COMMIT_SHA=${{ github.sha }} \ - --app ${{ steps.app_name.outputs.value }}-staging - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - - name: πŸ“¦ Build Production Container - if: ${{ github.ref == 'refs/heads/main' }} run: | flyctl deploy \ --build-only \ @@ -186,15 +176,18 @@ jobs: --build-arg COMMIT_SHA=${{ github.sha }} \ --build-secret SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} \ --app ${{ steps.app_name.outputs.value }} - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - deploy: - name: πŸš€ Deploy + deploy-staging: + name: 🚁 Deploy staging app for PR runs-on: ubuntu-24.04 - needs: [lint, typecheck, vitest, playwright, container] - # only deploy on pushes - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'pull_request' }} + outputs: + url: ${{ steps.deploy.outputs.url }} + concurrency: + group: pr-${{ github.event.number }} + environment: + name: staging + url: ${{ steps.deploy.outputs.url }} steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 @@ -211,19 +204,63 @@ jobs: - name: 🎈 Setup Fly uses: superfly/flyctl-actions/setup-flyctl@1.5 - - name: πŸš€ Deploy Staging - if: ${{ github.ref == 'refs/heads/dev' }} + - name: πŸ—οΈ Create Fly app and provision resources + if: github.event.action != 'closed' run: | - flyctl deploy \ - --image "registry.fly.io/${{ steps.app_name.outputs.value }}-staging:${{ github.sha }}" \ - --app ${{ steps.app_name.outputs.value }}-staging - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} + FLY_APP_NAME="${{ steps.app_name.outputs.value }}-pr-${{ github.event.number }}" + FLY_REGION=$(flyctl config show | jq -r '.primary_region') + + # Create app if it doesn't exist + if ! flyctl status --app "$FLY_APP_NAME"; then + # change org name if needed + flyctl apps create $FLY_APP_NAME --org $FLY_ORG + flyctl secrets --app $FLY_APP_NAME set SESSION_SECRET=$(openssl rand -hex 32) HONEYPOT_SECRET=$(openssl rand -hex 32) + flyctl volumes create data --app $FLY_APP_NAME --region $FLY_REGION --size 1 --yes + flyctl consul attach --app $FLY_APP_NAME + # Don't log the created tigris secrets! + flyctl storage create --app $FLY_APP_NAME --name epic-stack-$FLY_APP_NAME --yes > /dev/null 2>&1 + fi + + - name: 🚁 Deploy PR app to Fly.io + id: deploy + uses: superfly/fly-pr-review-apps@1.5.0 + with: + name: ${{ steps.app_name.outputs.value }}-pr-${{ github.event.number }} + secrets: | + APP_ENV=staging + ALLOW_INDEXING=false + SENTRY_DSN=${{ secrets.SENTRY_DSN }} + RESEND_API_KEY=${{ secrets.RESEND_API_KEY }} + build_args: | + COMMIT_SHA=${{ github.sha }} + build_secrets: | + SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} + + deploy: + name: πŸš€ Deploy production + runs-on: ubuntu-24.04 + needs: [lint, typecheck, vitest, playwright, container] + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + environment: + name: production + url: https://${{ steps.app_name.outputs.value }}.fly.dev + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: '50' + + - name: πŸ‘€ Read app name + uses: SebRollen/toml-action@v1.2.0 + id: app_name + with: + file: 'fly.toml' + field: 'app' + + - name: 🎈 Setup Fly + uses: superfly/flyctl-actions/setup-flyctl@1.5 - name: πŸš€ Deploy Production - if: ${{ github.ref == 'refs/heads/main' }} run: | flyctl deploy \ --image "registry.fly.io/${{ steps.app_name.outputs.value }}:${{ github.sha }}" - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/docs/database.md b/docs/database.md index 746714d37..e350c450e 100644 --- a/docs/database.md +++ b/docs/database.md @@ -148,14 +148,24 @@ migrations. ## Seeding Production In this application we have Role-based Access Control implemented. We initialize -the database with `admin` and `user` roles with appropriate permissions. +the database with `admin` and `user` roles with appropriate permissions. This is +done in the `migration.sql` file that's included in the template. -This is done in the `migration.sql` file that's included in the template. If you -need to seed the production database, modifying migration files manually is the -recommended approach to ensure it's reproducible. +For staging we create a new database for each PR. To make sure that this +database is already filled with some seed data we manually run the following +command: + +```sh +npx prisma db execute --file ./prisma/seed.staging.sql --url $DATABASE_URL +``` + +If you need to seed the production database, modifying migration files manually +is the recommended approach to ensure it's reproducible. The trick is not all of us are really excited about writing raw SQL (especially -if what you need to seed is a lot of data), so here's an easy way to help out: +if what you need to seed is a lot of data). You could look at `seed.staging.sql` +for inspiration or create a custom sql migration file with the following steps. +You can also use these steps to modify the seed.staging.sql file to your liking. 1. Create a script very similar to our `prisma/seed.ts` file which creates all the data you want to seed. @@ -300,7 +310,6 @@ You've got a few options: re-generating the migration after fixing the error. 3. If you do care about the data and don't have a backup, you can follow these steps: - 1. Comment out the [`exec` section from `litefs.yml` file](https://github.com/epicweb-dev/epic-stack/blob/main/other/litefs.yml#L31-L37). diff --git a/docs/decisions/047-pr-staging-environments.md b/docs/decisions/047-pr-staging-environments.md new file mode 100644 index 000000000..0412a5f69 --- /dev/null +++ b/docs/decisions/047-pr-staging-environments.md @@ -0,0 +1,48 @@ +# Per-PR Staging Environments + +Date: 2025-12-24 + +Status: accepted + +## Context + +The Epic Stack previously used a single shared staging environment deployed from the `dev` branch. This approach created several challenges for teams working with multiple pull requests: + +- **Staging bottleneck**: Only one PR could be properly tested in the staging environment at a time, making parallel development difficult. +- **Unclear test failures**: When QA testing failed, it was hard to determine if the failure was from the specific PR being tested or from other changes that had been deployed to the shared staging environment. +- **Serial workflow**: Teams couldn't perform parallel quality assurance, forcing them to coordinate who could use staging at any given time. +- **Extra setup complexity**: During initial deployment, users had to create and configure a separate staging app with its own database, secrets, and resources. + +Fly.io provides native support for PR preview environments through their `fly-pr-review-apps` GitHub Action, which can automatically create, update, and destroy ephemeral applications for each pull request. + +This pattern is common in modern deployment workflows (Vercel, Netlify, Render, etc.) and provides isolated environments for testing changes before they reach production. + +## Decision + +We've decided to replace the single shared staging environment with per-PR staging environments using Fly.io's PR review apps feature. Each pull request now: + +- Gets its own isolated Fly.io application (e.g., `app-name-pr-123`) +- Automatically provisions all necessary resources (SQLite volume, Tigris object storage, Consul for LiteFS) +- Generates and stores secrets (SESSION_SECRET, HONEYPOT_SECRET) +- Seeds the database with test data for immediate usability +- Provides a direct URL to the deployed app in the GitHub PR interface +- Automatically cleans up all resources when the PR is closed + +Staging environment secrets are now managed as GitHub environment secrets and passed to Fly in Github Actions. + +The `dev` branch and its associated staging app have been removed from the deployment workflow. Production deployments continue to run only on pushes to the `main` branch. + +## Consequences + +**Positive:** + +- **Isolated testing**: Each PR has its own complete environment, making it clear which changes caused any issues +- **Simplified onboarding**: New users only need to set up one production app, not both production and staging +- **Better reviews**: Reviewers (including non-technical stakeholders) can click a link to see and interact with changes before merging +- **Automatic cleanup**: Resources are freed when PRs close, reducing infrastructure costs +- **Realistic testing**: Each PR tests the actual deployment process, catching deployment-specific issues early + +**Negative:** + +- **Increased resource usage during development**: Each open PR consumes Fly.io resources (though they're automatically cleaned up) + diff --git a/docs/deployment.md b/docs/deployment.md index bc1737358..2cec5a64e 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -9,11 +9,19 @@ Here they are! Prior to your first deployment, you'll need to do a few things: +1. [Install the Github CLI](https://cli.github.com/) + +1. Login to GitHub: + + ```sh + gh auth login + ``` + 1. [Install Fly](https://fly.io/docs/getting-started/installing-flyctl/). > **Note**: Try `flyctl` instead of `fly` if the commands below won't work. -2. Sign up and log in to Fly: +1. Sign up and log in to Fly: ```sh fly auth signup @@ -24,17 +32,15 @@ Prior to your first deployment, you'll need to do a few things: > terminal, run `fly auth whoami` and ensure the email matches the Fly > account signed into the browser. -3. Create two apps on Fly, one for staging and one for production: +1. Create a Fly app for production: ```sh fly apps create [YOUR_APP_NAME] - fly apps create [YOUR_APP_NAME]-staging ``` - > **Note**: Make sure this name matches the `app` set in your `fly.toml` - > file. Otherwise, you will not be able to deploy. +1. Change the app name in fly.toml to name of the app you just created. -4. Initialize Git. +1. Initialize Git. ```sh git init @@ -47,80 +53,74 @@ Prior to your first deployment, you'll need to do a few things: git remote add origin ``` -5. Add secrets: +1. Add secrets: -- Add a `FLY_API_TOKEN` to your GitHub repo. To do this, go to your user - settings on Fly and create a new - [token](https://web.fly.io/user/personal_access_tokens/new), then add it to - [your repo secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) - with the name `FLY_API_TOKEN`. - -- Add a `SESSION_SECRET` and `HONEYPOT_SECRET` to your fly app secrets, to do - this you can run the following commands: +- Create a `FLY_API_TOKEN` by running: ```sh - fly secrets set SESSION_SECRET=$(openssl rand -hex 32) HONEYPOT_SECRET=$(openssl rand -hex 32) --app [YOUR_APP_NAME] - fly secrets set SESSION_SECRET=$(openssl rand -hex 32) HONEYPOT_SECRET=$(openssl rand -hex 32) --app [YOUR_APP_NAME]-staging + fly tokens org ``` - > **Note**: If you don't have openssl installed, you can also use - > [1Password](https://1password.com/password-generator) to generate a random - > secret, just replace `$(openssl rand -hex 32)` with the generated secret. +- Add this token to your GitHub repo: -- Add a `ALLOW_INDEXING` with `false` value to your non-production fly app - secrets, this is to prevent duplicate content from being indexed multiple - times by search engines. To do this you can run the following commands: + ```sh + gh secret set FLY_API_TOKEN --body "" + ``` + +- Add a `SESSION_SECRET` and `HONEYPOT_SECRET` to your fly app secrets for + production: ```sh - fly secrets set ALLOW_INDEXING=false --app [YOUR_APP_NAME]-staging + fly secrets set SESSION_SECRET=$(openssl rand -hex 32) HONEYPOT_SECRET=$(openssl rand -hex 32) ``` -6. Create production database: +> **Note**: If you don't have openssl installed, you can also use +> [1Password](https://1password.com/password-generator) to generate a random +> secret, just replace `$(openssl rand -hex 32)` with the generated secret. + +1. Create production database: - Create a persistent volume for the sqlite database for both your staging and - production environments. Run the following (feel free to change the GB size - based on your needs and the region of your choice + Create a persistent volume for the sqlite database for your production + environment. Run the following (feel free to change the GB size based on your + needs and the region of your choice (`https://fly.io/docs/reference/regions/`). If you do change the region, make sure you change the `primary_region` in fly.toml as well): ```sh - fly volumes create data --region sjc --size 1 --app [YOUR_APP_NAME] - fly volumes create data --region sjc --size 1 --app [YOUR_APP_NAME]-staging + fly volumes create data --region sjc --size 1 ``` -7. Attach Consul: +1. Attach Consul: - Consul is a fly-managed service that manages your primary instance for data replication ([learn more about configuring consul](https://fly.io/docs/litefs/getting-started/#lease-configuration)). ```sh - fly consul attach --app [YOUR_APP_NAME] - fly consul attach --app [YOUR_APP_NAME]-staging + fly consul attach ``` -8. Set up Tigris object storage: +1. Set up Tigris object storage: ```sh - fly storage create --app [YOUR_APP_NAME] - fly storage create --app [YOUR_APP_NAME]-staging + fly storage create ``` - This will create a Tigris object storage bucket for both your production and - staging environments. The bucket will be used for storing uploaded files and - other objects in your application. This will also automatically create the + This will create a Tigris object storage bucket for your production + environment. The bucket will be used for storing uploaded files and other + objects in your application. This will also automatically create the necessary environment variables for your app. During local development, this is completely mocked out so you don't need to worry about it. -9. Commit! +1. Commit! The Epic Stack comes with a GitHub Action that handles automatically deploying your app to production and staging environments. Now that everything is set up you can commit and push your changes to your repo. Every commit to your `main` branch will trigger a deployment to your - production environment, and every commit to your `dev` branch will trigger a - deployment to your staging environment. + production environment, and every commit to a PR will trigger a deployment to + your staging environment. --- diff --git a/docs/email.md b/docs/email.md index c42d80f8b..e39525d38 100644 --- a/docs/email.md +++ b/docs/email.md @@ -13,7 +13,8 @@ both prod and staging: ```sh fly secrets set RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh" --app [YOUR_APP_NAME] -fly secrets set RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh" --app [YOUR_APP_NAME]-staging +# See how to install gh: https://cli.github.com/ +gh secret set RESEND_API_KEY -e staging --body "re_blAh_blaHBlaHblahBLAhBlAh" ``` Setup a [custom sending domain](https://resend.com/domains) and then make sure diff --git a/docs/monitoring.md b/docs/monitoring.md index ec354fc86..2c5f95a39 100644 --- a/docs/monitoring.md +++ b/docs/monitoring.md @@ -23,11 +23,13 @@ filling out the signup form. ## Setting up the sentry-vite plugin Once you see the onboarding page which has the DSN, copy that somewhere (this -becomes `SENTRY_DSN`). Now, set the sentry dsn secret in Fly.io to be used as an -env var during runtime: +becomes `SENTRY_DSN`). Now, set the sentry dsn secret for production and +staging: ```sh -fly secrets set SENTRY_DSN= +fly secrets set SENTRY_DSN= --app [YOUR_APP_NAME] +# See how to install gh: https://cli.github.com/ +gh secret set SENTRY_DSN -e staging --body "" ``` See the guides for React Router v7 diff --git a/docs/secrets.md b/docs/secrets.md index 9aca01351..f766685db 100644 --- a/docs/secrets.md +++ b/docs/secrets.md @@ -25,13 +25,23 @@ so you can interact with the real service if you need to during development. ## Production secrets -To publish a secret to your production and staging applications, you can use the -`fly secrets set` command. For example, if you were integrating with the `tito` -API, to set the `TITO_API_SECRET` secret, you would run the following command: +To publish a secret to your production application, you can use the +`fly secrets set` command. For staging, use GitHub environment secrets. For +example, if you were integrating with the `tito` API, to set the +`TITO_API_SECRET` secret, you would run the following commands: ```sh -fly secrets set TITO_API_SECRET=some_secret_value -fly secrets set TITO_API_SECRET=some_secret_value --app [YOUR_STAGING_APP_NAME] +fly secrets set TITO_API_SECRET=some_secret_value --app [YOUR_APP_NAME] +# See how to install gh: https://cli.github.com/ +gh secret set TITO_API_SECRET -e staging --body "some_secret_value" ``` -This will redeploy your app with that environment variable set. +Also add the secret to the staging `secrets` section in +.github/workflows/deploy.yml + +```yaml +secrets: | + TITO_API_SECRET=${{ secrets.TITO_API_SECRET }} +``` + +This will make the secret available to your production and staging environments. diff --git a/fly.toml b/fly.toml index 7a24ed239..66cdc4c63 100644 --- a/fly.toml +++ b/fly.toml @@ -1,5 +1,5 @@ -app = "epic-stack-template" -primary_region = "sjc" +app = "epic-rsc-stack" +primary_region = "ams" kill_signal = "SIGINT" kill_timeout = 5 processes = [ ] diff --git a/other/litefs.yml b/other/litefs.yml index 0bc4dd922..927289486 100644 --- a/other/litefs.yml +++ b/other/litefs.yml @@ -40,6 +40,10 @@ exec: - cmd: sqlite3 $CACHE_DATABASE_PATH "PRAGMA journal_mode = WAL;" if-candidate: true + # Seed staging database with test data + - cmd: sh -c '[ "$APP_ENV" = "staging" ] && npx prisma db execute --file ./prisma/seed.staging.sql --url $DATABASE_URL' + if-candidate: true + # Generate Typed SQL files - cmd: npx prisma generate --sql diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index e1640d1f2..2a5a44419 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (e.g., Git) -provider = "sqlite" \ No newline at end of file +provider = "sqlite" diff --git a/prisma/seed.staging.sql b/prisma/seed.staging.sql new file mode 100644 index 000000000..a75e0d6b2 --- /dev/null +++ b/prisma/seed.staging.sql @@ -0,0 +1,43 @@ +INSERT OR IGNORE INTO User VALUES('cmjj43ip8000087g3cl45r1bl','eh_peyton_schmeler@example.com','eh_peyton_schmeler','Peyton Schmeler',1766526214508,1766526214508); +INSERT OR IGNORE INTO User VALUES('cmjj43irn000f87g3d7lga2uw','s6_mary_marks22@example.com','s6_mary_marks22','Mary Marks',1766526214596,1766526214596); +INSERT OR IGNORE INTO User VALUES('cmjj43itx000w87g3zk4j9whe','qb_stephen_klein@example.com','qb_stephen_klein','Stephen Klein',1766526214677,1766526214677); +INSERT OR IGNORE INTO User VALUES('cmjj43ivy001387g3tve6whk0','mf_braxton_dare21@example.com','mf_braxton_dare21','Braxton Dare',1766526214751,1766526214751); +INSERT OR IGNORE INTO User VALUES('cmjj43iy7001q87g31bujwkey','a8_elisa_walsh@example.com','a8_elisa_walsh','Elisa Walsh',1766526214831,1766526214831); +INSERT OR IGNORE INTO User VALUES('cmjj43j0c001z87g33jd5ux34','kody@kcd.dev','kody','Kody',1766526214908,1766526214908); + +INSERT OR IGNORE INTO Note VALUES('cmjj43ipe000487g3b8s05fgp','Cura vomica cetera ipsam victoria reiciendis deripio pectus alioqui.',replace('Corporis conor amoveo comparo suasoria vulticulus auxilium. Sollicito suus ex vis. Necessitatibus vis crudelis sui temptatio assumenda numquam barba adopto.\nSollicito vilicus teneo agnitio acies. Arcesso avarus titulus terminatio pauci celebrer clarus depereo summopere. Tego eveniet ullus trepide cuius verbera magnam catena cimentarius.\nTextilis beatae cursus depopulo xiphias reiciendis suasoria. Denego tremo natus aequitas ambitus ulterius reiciendis. Suggero sui optio.','\n',char(10)),1766526214514,1766526214514,'cmjj43ip8000087g3cl45r1bl'); +INSERT OR IGNORE INTO Note VALUES('cmjj43ipf000887g3stop05lz','Sint considero viduo villa omnis.',replace('Arcus adsidue termes timor vociferor. Acquiro decet abeo ipsum sollers creator voluptatum adsidue. Dignissimos curvo beatae vomica carmen vox arceo.\nEst culpo corona ater tribuo cunabula aequitas celebrer conor vetus. Arbustum arbustum saepe colo ascit summisse. Somnus desparatus tantillus minus aliquam cursim viriliter theologus asper curvo.\nTergum cauda tribuo cupressus blandior velum tredecim solutio. Cattus deleo voluptates. Correptius abduco adimpleo amplitudo doloremque sit celo deporto.','\n',char(10)),1766526214516,1766526214516,'cmjj43ip8000087g3cl45r1bl'); +INSERT OR IGNORE INTO Note VALUES('cmjj43irq000j87g3aqf76qov','Temptatio temperantia molestias.',replace('Tersus denuncio clam. Coadunatio vitiosus adinventitias. Comis arbor capto timidus caveo voluptas patria contigo vinitor adsuesco.\nApto sono vereor. Demo expedita alveus considero doloremque votum. Fuga apostolus arguo antepono numquam vinum angustus ulciscor.\nSuspendo atrox deorsum ascit delego curtus vulnero dolorem temeritas comes. Urbs quam studio usque deripio tergo coniuratio ancilla. Demo amita cilicium molestias sequi civitas eos cubo.','\n',char(10)),1766526214599,1766526214599,'cmjj43irn000f87g3d7lga2uw'); +INSERT OR IGNORE INTO Note VALUES('cmjj43irt000p87g3p9l5txoi','Vinum sunt attonbitus vox trado aestas talus.',replace('Vestrum solio ultra. Tantum omnis aperiam sonitus vindico. Error acervus summisse nulla universe defungo.\nAmissio rerum cuppedia conturbo confugo theatrum depraedor audacia sono conqueror. Vitiosus congregatio cogo angelus sono tripudio. Tantum celo compono abstergo patrocinor demum adimpleo complectus altus at.\nPlaceat cruentus aufero supplanto decerno defleo. Urbanus trucido voluptatem voro animi casso amplexus desparatus calculus. Vetus cibo vesica.','\n',char(10)),1766526214602,1766526214602,'cmjj43irn000f87g3d7lga2uw'); +INSERT OR IGNORE INTO Note VALUES('cmjj43itz001087g38katdf10','Socius curtus altus amo unde.',replace('Sapiente vilis tondeo tenax. Tertius doloremque pax ulciscor modi quos cariosus. Cogito congregatio rerum autem texo vulgaris.\nCommodo abeo defungo fugit sum clamo. Iste ambitus confugo correptius facere. Valde verto tracto adinventitias convoco alo.\nAlo ullus suscipio quasi terror decet admiratio tabella iure repudiandae. Subvenio decimus vos veritas arbustum. Cetera tam timidus acervus paens conscendo patior caste.','\n',char(10)),1766526214680,1766526214680,'cmjj43itx000w87g3zk4j9whe'); +INSERT OR IGNORE INTO Note VALUES('cmjj43iw0001787g3svld7qjv','Doloremque aegre stabilis assentator.',replace('Ubi acceptus candidus suffoco labore suscipit expedita charisma. Vae odio at sumo. Cur traho doloribus.\nVentosus demulceo natus arceo. Crur decimus dedecor accommodo succedo cupiditate adamo. Depraedor amo certus curia eum.\nAdhaero derideo angulus aliqua. Aliquam compono cupiditate tamquam argentum suppono victus fuga eius suadeo. Atrox sumptus advoco aperte succedo attonbitus eos tripudio caute et.','\n',char(10)),1766526214753,1766526214753,'cmjj43ivy001387g3tve6whk0'); +INSERT OR IGNORE INTO Note VALUES('cmjj43iw5001f87g3erj40rsv','Vacuus virgo decerno quasi viduo bellicus crepusculum quos calamitas spiritus.',replace('Urbs viriliter odit facilis nam adeo. Crudelis vicissitudo curtus decet. Nesciunt talio curiositas deinde stipes tametsi angustus considero curia.\nAudax anser ara adfero. Armarium stillicidium absorbeo vicissitudo circumvenio ubi coruscus villa. Vociferor termes similique audentia civis.\nAttero auctor aspicio minus amo circumvenio curatio. Repudiandae atrocitas terebro barba cur cariosus. Urbanus animadverto sit solus vestigium aro textus desipio.','\n',char(10)),1766526214757,1766526214757,'cmjj43ivy001387g3tve6whk0'); +INSERT OR IGNORE INTO Note VALUES('cmjj43iw7001l87g3vo4jr9oo','Aveho temporibus velum inflammatio ustilo contabesco repudiandae coerceo.',replace('Amiculum capillus abbas. Demulceo commodo velut avarus laborum ocer. Suppono commemoro amitto apparatus.\nUstulo calco timor amplitudo suasoria super asper tristis armarium. Sollers abbas antepono abutor arto torqueo eaque. Audacia ustulo amaritudo suus cetera verumtamen adhaero.\nSupplanto surgo spes. Aspernatur sui illo acquiro curvo. Textus altus tunc commemoro vulticulus talis nisi denuncio antiquus.','\n',char(10)),1766526214760,1766526214760,'cmjj43ivy001387g3tve6whk0'); +INSERT OR IGNORE INTO Note VALUES('cmjj43iy9001u87g335w60hdn','Quidem virga quis trepide.',replace('Adduco utroque desidero votum. Animus spectaculum omnis auctus aveho tum unus officiis. Articulus cenaculum balbus.\nAd socius debeo amitto recusandae. Inventore ait vulpes colligo cogito umerus arguo solutio auxilium cenaculum. Addo auxilium utroque thalassinus corroboro summa amicitia quidem angustus vomer.\nSumo blandior deludo exercitationem. Benigne calco spiculum necessitatibus commemoro tardus uredo. Amiculum usus atqui taceo umbra ustulo timidus tubineus caste vivo.','\n',char(10)),1766526214833,1766526214833,'cmjj43iy7001q87g31bujwkey'); +INSERT OR IGNORE INTO Note VALUES('d27a197e','Basic Koala Facts','Koalas are found in the eucalyptus forests of eastern Australia. They have grey fur with a cream-coloured chest, and strong, clawed feet, perfect for living in the branches of trees!',1766526214911,1766526214911,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('414f0c09','Koalas like to cuddle','Cuddly critters, koalas measure about 60cm to 85cm long, and weigh about 14kg.',1766526214912,1766526214912,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('260366b1','Not bears','Although you may have heard people call them koala ''bears'', these awesome animals aren''t bears at all – they are in fact marsupials. A group of mammals, most marsupials have pouches where their newborns develop.',1766526214913,1766526214913,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('bb79cf45','Snowboarding Adventure','Today was an epic day on the slopes! Shredded fresh powder with my friends, caught some sick air, and even attempted a backflip. Can''t wait for the next snowy adventure!',1766526214914,1766526214914,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('9f4308be','Onewheel Tricks','Mastered a new trick on my Onewheel today called ''180 Spin''. It''s exhilarating to carve through the streets while pulling off these rad moves. Time to level up and learn more!',1766526214914,1766526214914,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('306021fb','Coding Dilemma','Stuck on a bug in my latest coding project. Need to figure out why my function isn''t returning the expected output. Time to dig deep, debug, and conquer this challenge!',1766526214915,1766526214915,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('16d4912a','Coding Mentorship','Had a fantastic coding mentoring session today with Sarah. Helped her understand the concept of recursion, and she made great progress. It''s incredibly fulfilling to help others improve their coding skills.',1766526214916,1766526214916,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('3199199e','Koala Fun Facts','Did you know that koalas sleep for up to 20 hours a day? It''s because their diet of eucalyptus leaves doesn''t provide much energy. But when I''m awake, I enjoy munching on leaves, chilling in trees, and being the cuddliest koala around!',1766526214916,1766526214916,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('2030ffd3','Skiing Adventure','Spent the day hitting the slopes on my skis. The fresh powder made for some incredible runs and breathtaking views. Skiing down the mountain at top speed is an adrenaline rush like no other!',1766526214918,1766526214918,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('f375a804','Code Jam Success','Participated in a coding competition today and secured the first place! The adrenaline, the challenging problems, and the satisfaction of finding optimal solutionsβ€”it was an amazing experience. Feeling proud and motivated to keep pushing my coding skills further!',1766526214919,1766526214919,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('562c541b','Koala Conservation Efforts','Joined a local conservation group to protect koalas and their habitats. Together, we''re planting more eucalyptus trees, raising awareness about their endangered status, and working towards a sustainable future for these adorable creatures. Every small step counts!',1766526214920,1766526214920,'cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO Note VALUES('f67ca40b','Game day',replace('Just got back from the most amazing game. I''ve been playing soccer for a long time, but I''ve not once scored a goal. Well, today all that changed! I finally scored my first ever goal.\n\nI''m in an indoor league, and my team''s not the best, but we''re pretty good and I have fun, that''s all that really matters. Anyway, I found myself at the other end of the field with the ball. It was just me and the goalie. I normally just kick the ball and hope it goes in, but the ball was already rolling toward the goal. The goalie was about to get the ball, so I had to charge. I managed to get possession of the ball just before the goalie got it. I brought it around the goalie and had a perfect shot. I screamed so loud in excitement. After all these years playing, I finally scored a goal!\n\nI know it''s not a lot for most folks, but it meant a lot to me. We did end up winning the game by one. It makes me feel great that I had a part to play in that.\n\nIn this team, I''m the captain. I''m constantly cheering my team on. Even after getting injured, I continued to come and watch from the side-lines. I enjoy yelling (encouragingly) at my team mates and helping them be the best they can. I''m definitely not the best player by a long stretch. But I really enjoy the game. It''s a great way to get exercise and have good social interactions once a week.\n\nThat said, it can be hard to keep people coming and paying dues and stuff. If people don''t show up it can be really hard to find subs. I have a list of people I can text, but sometimes I can''t find anyone.\n\nBut yeah, today was awesome. I felt like more than just a player that gets in the way of the opposition, but an actual asset to the team. Really great feeling.\n\nAnyway, I''m rambling at this point and really this is just so we can have a note that''s pretty long to test things out. I think it''s long enough now... Cheers!','\n',char(10)),1766526214921,1766526214921,'cmjj43j0c001z87g33jd5ux34'); + +INSERT OR IGNORE INTO Password VALUES('$2b$10$Ma18jaNeY8HG5dBTsrRAZOKkaddfTjKvc.iJZPOlLYv3lkch11EDa','cmjj43ip8000087g3cl45r1bl'); +INSERT OR IGNORE INTO Password VALUES('$2b$10$8bMjwbn212ckanCg/W46de/lIIj8VIz5mtwRbyxtZTSrCV5xoeSwS','cmjj43irn000f87g3d7lga2uw'); +INSERT OR IGNORE INTO Password VALUES('$2b$10$M8TyZ8wAgVE8ILpx5A9/d.8PT1vEEyHcrpYy4QXYKVz79gv7L6eni','cmjj43itx000w87g3zk4j9whe'); +INSERT OR IGNORE INTO Password VALUES('$2b$10$SREoCN/8lN8IQD.7L9Jdd.5wBfkaMItoSgRdiaOWKxr4FEDm7m9LG','cmjj43ivy001387g3tve6whk0'); +INSERT OR IGNORE INTO Password VALUES('$2b$10$fyagQiSrjrrWP0ue.h5d/OPeGhp0r6o17ooZ5wpFELvsNUhxXh2Cm','cmjj43iy7001q87g31bujwkey'); +INSERT OR IGNORE INTO Password VALUES('$2b$10$skTsfTwG8pN5H1AvBcYjmezpEuHpj6mlIQTaJ.KxYEt7JdfEVijSu','cmjj43j0c001z87g33jd5ux34'); + +INSERT OR IGNORE INTO _RoleToUser VALUES('clnf2zvlx000hpcou5dfrbegs','cmjj43ip8000087g3cl45r1bl'); +INSERT OR IGNORE INTO _RoleToUser VALUES('clnf2zvlx000hpcou5dfrbegs','cmjj43irn000f87g3d7lga2uw'); +INSERT OR IGNORE INTO _RoleToUser VALUES('clnf2zvlx000hpcou5dfrbegs','cmjj43itx000w87g3zk4j9whe'); +INSERT OR IGNORE INTO _RoleToUser VALUES('clnf2zvlx000hpcou5dfrbegs','cmjj43ivy001387g3tve6whk0'); +INSERT OR IGNORE INTO _RoleToUser VALUES('clnf2zvlx000hpcou5dfrbegs','cmjj43iy7001q87g31bujwkey'); +INSERT OR IGNORE INTO _RoleToUser VALUES('clnf2zvlw000gpcour6dyyuh6','cmjj43j0c001z87g33jd5ux34'); +INSERT OR IGNORE INTO _RoleToUser VALUES('clnf2zvlx000hpcou5dfrbegs','cmjj43j0c001z87g33jd5ux34'); diff --git a/remix.init/index.mjs b/remix.init/index.mjs index 3ed8d1104..8924c084d 100644 --- a/remix.init/index.mjs +++ b/remix.init/index.mjs @@ -180,46 +180,20 @@ async function setupDeployment({ rootDirectory }) { const { app: APP_NAME } = flyConfig - const { shouldSetupStaging } = await inquirer.prompt([ - { - name: 'shouldSetupStaging', - type: 'confirm', - default: true, - message: 'Would you like to set up a staging environment?', - }, - ]) - - console.log( - `πŸ₯ͺ Creating app ${APP_NAME}${shouldSetupStaging ? ' and staging...' : '...'}`, - ) - if (shouldSetupStaging) { - await $I`fly apps create ${APP_NAME}-staging` - } + console.log(`πŸ₯ͺ Creating app ${APP_NAME}`) await $I`fly apps create ${APP_NAME}` console.log(`🀫 Setting secrets in apps`) - if (shouldSetupStaging) { - await $I`fly secrets set SESSION_SECRET=${getRandomString32()} INTERNAL_COMMAND_TOKEN=${getRandomString32()} HONEYPOT_SECRET=${getRandomString32()} ALLOW_INDEXING=false --app ${APP_NAME}-staging` - } - await $I`fly secrets set SESSION_SECRET=${getRandomString32()} INTERNAL_COMMAND_TOKEN=${getRandomString32()} HONEYPOT_SECRET=${getRandomString32()} --app ${APP_NAME}` + await $I`fly secrets set SESSION_SECRET=${getRandomString32()} HONEYPOT_SECRET=${getRandomString32()} --app ${APP_NAME}` console.log(`πŸ”Š Creating volumes.`) - if (shouldSetupStaging) { - await $I`fly volumes create data --region ${primaryRegion} --size 1 --yes --app ${APP_NAME}-staging` - } await $I`fly volumes create data --region ${primaryRegion} --size 1 --yes --app ${APP_NAME}` console.log(`πŸ”— Attaching consul`) - if (shouldSetupStaging) { - await $I`fly consul attach --app ${APP_NAME}-staging` - } await $I`fly consul attach --app ${APP_NAME}` console.log(`πŸ—„οΈ Setting up Tigris object storage`) const $S = $({ stdio: ['inherit', 'ignore', 'inherit'], cwd: rootDirectory }) - if (shouldSetupStaging) { - await $S`fly storage create --yes --app ${APP_NAME}-staging --name epic-stack-${APP_NAME}-staging` - } await $S`fly storage create --yes --app ${APP_NAME} --name epic-stack-${APP_NAME}` const { shouldDeploy } = await inquirer.prompt([ @@ -233,12 +207,6 @@ async function setupDeployment({ rootDirectory }) { ]) if (shouldDeploy) { console.log(`πŸš€ Deploying`) - if (shouldSetupStaging) { - console.log(` Starting with staging`) - await $I`fly deploy --app ${APP_NAME}-staging` - await open(`https://${APP_NAME}-staging.fly.dev/`) - console.log(` Staging deployed... Deploying production...`) - } await $I`fly deploy --app ${APP_NAME}` await open(`https://${APP_NAME}.fly.dev/`) console.log(` Production deployed...`)