Skip to content

Commit

Permalink
chore(examples): bring blog-tutorial example in line with `indie-st…
Browse files Browse the repository at this point in the history
…ack` (remix-run#3070)
  • Loading branch information
MichaelDeBoey authored May 3, 2022
1 parent 17d8c4d commit d6434c3
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 42 deletions.
3 changes: 0 additions & 3 deletions examples/blog-tutorial/.github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ jobs:
- name: 🛠 Setup Database
run: npx prisma migrate reset --force

- name: 🌱 Seed the Database
run: npx prisma db seed

- name: ⚙️ Build
run: npm run build

Expand Down
9 changes: 9 additions & 0 deletions examples/blog-tutorial/.gitpod.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM gitpod/workspace-full

# Install Fly
RUN curl -L https://fly.io/install.sh | sh
ENV FLYCTL_INSTALL="/home/gitpod/.fly"
ENV PATH="$FLYCTL_INSTALL/bin:$PATH"

# Install GitHub CLI
RUN brew install gh
48 changes: 48 additions & 0 deletions examples/blog-tutorial/.gitpod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# https://www.gitpod.io/docs/config-gitpod-file

image:
file: .gitpod.Dockerfile

ports:
- port: 3000
onOpen: notify

tasks:
- name: Restore .env file
command: |
if [ -f .env ]; then
# If this workspace already has a .env, don't override it
# Local changes survive a workspace being opened and closed
# but they will not persist between separate workspaces for the same repo
echo "Found .env in workspace"
else
# There is no .env
if [ ! -n "${ENV}" ]; then
# There is no $ENV from a previous workspace
# Default to the example .env
echo "Setting example .env"
cp .env.example .env
else
# After making changes to .env, run this line to persist it to $ENV
# eval $(gp env -e ENV="$(base64 .env | tr -d '\n')")
#
# Environment variables set this way are shared between all your workspaces for this repo
# The lines below will read $ENV and print a .env file
echo "Restoring .env from Gitpod"
echo "${ENV}" | base64 -d | tee .env > /dev/null
fi
fi
- init: npm install
command: npm run setup && npm run dev

vscode:
extensions:
- ms-azuretools.vscode-docker
- esbenp.prettier-vscode
- dbaeumer.vscode-eslint
- bradlc.vscode-tailwindcss
9 changes: 8 additions & 1 deletion examples/blog-tutorial/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FROM node:16-bullseye-slim as base
ENV NODE_ENV production

# Install openssl for Prisma
RUN apt-get update && apt-get install -y openssl
RUN apt-get update && apt-get install -y openssl sqlite3

# Install all node_modules, including dev dependencies
FROM base as deps
Expand Down Expand Up @@ -40,6 +40,13 @@ RUN npm run build
# Finally, build the production image with minimal footprint
FROM base

ENV DATABASE_URL=file:/data/sqlite.db
ENV PORT="8080"
ENV NODE_ENV="production"

# add shortcut for connecting to database CLI
RUN echo "#!/bin/sh\nset -x\nsqlite3 \$DATABASE_URL" > /usr/local/bin/database-cli && chmod +x /usr/local/bin/database-cli

WORKDIR /myapp

COPY --from=production-deps /myapp/node_modules /myapp/node_modules
Expand Down
8 changes: 7 additions & 1 deletion examples/blog-tutorial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ npx create-remix --template remix-run/indie-stack

Not a fan of bits of the stack? Fork it, change it, and use `npx create-remix --template your/repo`! Make it your own.

## Quickstart

Click this button to create a [Gitpod](https://gitpod.io) workspace with the project set up and Fly pre-installed

[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/from-referrer/)

## Development

- Initial setup: _If you just generated this project, this step has been done for you._
Expand Down Expand Up @@ -93,7 +99,7 @@ Prior to your first deployment, you'll need to do a few things:
fly secrets set SESSION_SECRET=$(openssl rand -hex 32) --app blog-tutorial-ffb5-staging
```

If you don't have openssl installed, you can also use [1password](https://1password.com/generate-password) to generate a random secret, just replace `$(openssl rand -hex 32)` with the generated secret.
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.

- Create a persistent volume for the sqlite database for both your staging and production environments. Run the following:

Expand Down
3 changes: 2 additions & 1 deletion examples/blog-tutorial/app/routes/healthcheck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ export const loader: LoaderFunction = async ({ request }) => {
request.headers.get("X-Forwarded-Host") ?? request.headers.get("host");

try {
const url = new URL("/", `http://${host}`);
// if we can connect to the database and make a simple query
// and make a HEAD request to ourselves, then we're good.
await Promise.all([
prisma.user.count(),
fetch(`http://${host}`, { method: "HEAD" }).then((r) => {
fetch(url.toString(), { method: "HEAD" }).then((r) => {
if (!r.ok) return Promise.reject(r);
}),
]);
Expand Down
9 changes: 4 additions & 5 deletions examples/blog-tutorial/app/routes/join.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import { json, redirect } from "@remix-run/node";
import { Form, Link, useActionData, useSearchParams } from "@remix-run/react";
import * as React from "react";

import { getUserId, createUserSession } from "~/session.server";

import { createUser, getUserByEmail } from "~/models/user.server";
import { validateEmail } from "~/utils";
import { getUserId, createUserSession } from "~/session.server";
import { safeRedirect, validateEmail } from "~/utils";

export const loader: LoaderFunction = async ({ request }) => {
const userId = await getUserId(request);
Expand All @@ -29,7 +28,7 @@ export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
const redirectTo = formData.get("redirectTo");
const redirectTo = safeRedirect(formData.get("redirectTo"), "/");

if (!validateEmail(email)) {
return json<ActionData>(
Expand Down Expand Up @@ -66,7 +65,7 @@ export const action: ActionFunction = async ({ request }) => {
request,
userId: user.id,
remember: false,
redirectTo: typeof redirectTo === "string" ? redirectTo : "/",
redirectTo,
});
};

Expand Down
8 changes: 4 additions & 4 deletions examples/blog-tutorial/app/routes/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as React from "react";

import { createUserSession, getUserId } from "~/session.server";
import { verifyLogin } from "~/models/user.server";
import { validateEmail } from "~/utils";
import { safeRedirect, validateEmail } from "~/utils";

export const loader: LoaderFunction = async ({ request }) => {
const userId = await getUserId(request);
Expand All @@ -28,7 +28,7 @@ export const action: ActionFunction = async ({ request }) => {
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
const redirectTo = formData.get("redirectTo");
const redirectTo = safeRedirect(formData.get("redirectTo"), "/notes");
const remember = formData.get("remember");

if (!validateEmail(email)) {
Expand Down Expand Up @@ -65,7 +65,7 @@ export const action: ActionFunction = async ({ request }) => {
request,
userId: user.id,
remember: remember === "on" ? true : false,
redirectTo: typeof redirectTo === "string" ? redirectTo : "/notes",
redirectTo,
});
};

Expand Down Expand Up @@ -135,7 +135,7 @@ export default function LoginPage() {
ref={passwordRef}
name="password"
type="password"
autoComplete="new-password"
autoComplete="current-password"
aria-invalid={actionData?.errors?.password ? true : undefined}
aria-describedby="password-error"
className="w-full rounded border border-gray-500 px-2 py-1 text-lg"
Expand Down
4 changes: 2 additions & 2 deletions examples/blog-tutorial/app/routes/notes/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default function NewNotePage() {
/>
</label>
{actionData?.errors?.title && (
<Alert className="pt-1 text-red-700" id="title=error">
<Alert className="pt-1 text-red-700" id="title-error">
{actionData.errors.title}
</Alert>
)}
Expand All @@ -98,7 +98,7 @@ export default function NewNotePage() {
/>
</label>
{actionData?.errors?.body && (
<Alert className="pt-1 text-red-700" id="body=error">
<Alert className="pt-1 text-red-700" id="body-error">
{actionData.errors.body}
</Alert>
)}
Expand Down
8 changes: 5 additions & 3 deletions examples/blog-tutorial/app/session.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ export async function getSession(request: Request) {
return sessionStorage.getSession(cookie);
}

export async function getUserId(request: Request): Promise<string | undefined> {
export async function getUserId(
request: Request
): Promise<User["id"] | undefined> {
const session = await getSession(request);
const userId = session.get(USER_SESSION_KEY);
return userId;
}

export async function getUser(request: Request): Promise<null | User> {
export async function getUser(request: Request) {
const userId = await getUserId(request);
if (userId === undefined) return null;

Expand All @@ -44,7 +46,7 @@ export async function getUser(request: Request): Promise<null | User> {
export async function requireUserId(
request: Request,
redirectTo: string = new URL(request.url).pathname
): Promise<string> {
) {
const userId = await getUserId(request);
if (!userId) {
const searchParams = new URLSearchParams([["redirectTo", redirectTo]]);
Expand Down
24 changes: 24 additions & 0 deletions examples/blog-tutorial/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@ import { useMemo } from "react";

import type { User } from "~/models/user.server";

const DEFAULT_REDIRECT = "/";

/**
* This should be used any time the redirect path is user-provided
* (Like the query string on our login/signup pages). This avoids
* open-redirect vulnerabilities.
* @param {string} to The redirect destination
* @param {string} defaultRedirect The redirect to use if the to is unsafe.
*/
export function safeRedirect(
to: FormDataEntryValue | string | null | undefined,
defaultRedirect: string = DEFAULT_REDIRECT
) {
if (!to || typeof to !== "string") {
return defaultRedirect;
}

if (!to.startsWith("/") || to.startsWith("//")) {
return defaultRedirect;
}

return to;
}

/**
* This base hook is used in other hooks to quickly search for specific data
* across all loader data using useMatches.
Expand Down
2 changes: 1 addition & 1 deletion examples/blog-tutorial/mocks/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { setupServer } from "msw/node";

const server = setupServer();

server.listen({ onUnhandledRequest: "warn" });
server.listen({ onUnhandledRequest: "bypass" });
console.info("🔶 Mock server running");

process.once("SIGINT", () => server.close());
Expand Down
45 changes: 25 additions & 20 deletions examples/blog-tutorial/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,46 +32,51 @@
],
"dependencies": {
"@node-rs/bcrypt": "^1.6.0",
"@prisma/client": "^3.11.0",
"@reach/alert": "^0.16.0",
"@prisma/client": "^3.13.0",
"@reach/alert": "^0.17.0",
"@remix-run/node": "1.4.3",
"@remix-run/react": "1.4.3",
"@remix-run/serve": "1.4.3",
"marked": "^4.0.12",
"marked": "^4.0.15",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"tiny-invariant": "^1.2.0"
},
"devDependencies": {
"@faker-js/faker": "^6.0.0",
"@faker-js/faker": "^6.3.1",
"@remix-run/dev": "1.4.3",
"@remix-run/eslint-config": "1.4.3",
"@testing-library/cypress": "^8.0.2",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/dom": "^8.13.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^13.5.0",
"@types/eslint": "^8.4.1",
"@types/marked": "^4.0.2",
"@types/react": "^17.0.40",
"@types/react-dom": "^17.0.13",
"@vitejs/plugin-react": "^1.2.0",
"c8": "^7.11.0",
"@types/node": "^17.0.31",
"@types/marked": "^4.0.3",
"@types/react": "^17.0.44",
"@types/react-dom": "^17.0.16",
"@vitejs/plugin-react": "^1.3.2",
"autoprefixer": "^10.4.7",
"c8": "^7.11.2",
"cross-env": "^7.0.3",
"cypress": "^9.5.2",
"cypress": "^9.6.0",
"esbuild-register": "^3.3.2",
"eslint": "^8.11.0",
"eslint": "^8.14.0",
"eslint-config-prettier": "^8.5.0",
"happy-dom": "^2.49.0",
"happy-dom": "^3.1.0",
"msw": "^0.39.2",
"npm-run-all": "^4.1.5",
"prettier": "2.6.0",
"prettier-plugin-tailwindcss": "^0.1.8",
"prisma": "^3.11.0",
"postcss": "^8.4.13",
"prettier": "2.6.2",
"prettier-plugin-tailwindcss": "^0.1.10",
"prisma": "^3.13.0",
"start-server-and-test": "^1.14.0",
"tailwindcss": "^3.0.23",
"typescript": "^4.6.2",
"tailwindcss": "^3.0.24",
"typescript": "^4.6.4",
"vite": "^2.9.7",
"vite-tsconfig-paths": "^3.4.1",
"vitest": "^0.7.4"
"vitest": "^0.10.2"
},
"engines": {
"node": ">=14"
Expand Down
2 changes: 1 addition & 1 deletion examples/blog-tutorial/remix.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
*/
module.exports = {
cacheDirectory: "./node_modules/.cache/remix",
ignoredRouteFiles: [".*", "**/*.css", "**/*.test.{js,jsx,ts,tsx}"],
ignoredRouteFiles: ["**/.*", "**/*.css", "**/*.test.{js,jsx,ts,tsx}"],
};

0 comments on commit d6434c3

Please sign in to comment.