Skip to content

Commit ac27899

Browse files
committed
update to latest epic stack
epicweb-dev/epic-stack@ae5f305
1 parent d546dde commit ac27899

16 files changed

+1108
-840
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ DATABASE_URL="file:./data.db?connection_limit=1"
44
CACHE_DATABASE_PATH="./other/cache.db"
55
SESSION_SECRET="super-duper-s3cret"
66
HONEYPOT_SECRET="super-duper-s3cret"
7-
INTERNAL_COMMAND_TOKEN="some-made-up-token"
87
SENTRY_DSN="your-dsn"
98

9+
# this is set to a random value in the Dockerfile
10+
INTERNAL_COMMAND_TOKEN="some-made-up-token"
11+
1012
# set this to false to prevent search engines from indexing the website
1113
# default to allow indexing for seo safety
1214
ALLOW_INDEXING="true"

app/components/progress-bar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function EpicProgress() {
2222
.getAnimations()
2323
.map(({ finished }) => finished)
2424

25-
Promise.allSettled(animationPromises).then(() => {
25+
void Promise.allSettled(animationPromises).then(() => {
2626
if (!delayedPending) setAnimationComplete(true)
2727
})
2828
}, [delayedPending])

app/entry.client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { startTransition } from 'react'
33
import { hydrateRoot } from 'react-dom/client'
44

55
if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
6-
import('./utils/monitoring.client.tsx').then(({ init }) => init())
6+
void import('./utils/monitoring.client.tsx').then(({ init }) => init())
77
}
88

99
startTransition(() => {

app/entry.server.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ global.ENV = getEnv()
2323

2424
initCron()
2525

26-
if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
27-
import('./utils/monitoring.server.ts').then(({ init }) => init())
28-
}
29-
3026
type DocRequestArgs = Parameters<HandleDocumentRequestFunction>
3127

3228
export default async function handleRequest(...args: DocRequestArgs) {
@@ -43,11 +39,15 @@ export default async function handleRequest(...args: DocRequestArgs) {
4339
responseHeaders.set('fly-primary-instance', primaryInstance)
4440
responseHeaders.set('fly-instance', currentInstance)
4541

42+
if (process.env.NODE_ENV === 'production' && process.env.SENTRY_DSN) {
43+
responseHeaders.append('Document-Policy', 'js-profiling')
44+
}
45+
4646
const callbackName = isbot(request.headers.get('user-agent'))
4747
? 'onAllReady'
4848
: 'onShellReady'
4949

50-
const nonce = String(loadContext.cspNonce) ?? undefined
50+
const nonce = loadContext.cspNonce?.toString() ?? ''
5151
return new Promise(async (resolve, reject) => {
5252
let didError = false
5353
// NOTE: this timing will only include things that are rendered in the shell
@@ -106,7 +106,12 @@ export function handleError(
106106
}
107107
if (error instanceof Error) {
108108
console.error(chalk.red(error.stack))
109-
Sentry.captureRemixServerException(error, 'remix.server', request)
109+
void Sentry.captureRemixServerException(
110+
error,
111+
'remix.server',
112+
request,
113+
true,
114+
)
110115
} else {
111116
console.error(chalk.red(error))
112117
Sentry.captureException(error)

app/root.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export const links: LinksFunction = () => {
3838
return [
3939
// Preload svg sprite as a resource to avoid render blocking
4040
{ rel: 'preload', href: iconsHref, as: 'image' },
41-
// Preload CSS as a resource to avoid render blocking
4241
{ rel: 'mask-icon', href: '/favicons/mask-icon.svg' },
4342
{
4443
rel: 'alternate icon',
@@ -51,7 +50,6 @@ export const links: LinksFunction = () => {
5150
href: '/site.webmanifest',
5251
crossOrigin: 'use-credentials',
5352
} as const, // necessary to make typescript happy
54-
//These should match the css preloads above to avoid css as render blocking resource
5553
{ rel: 'icon', type: 'image/svg+xml', href: '/favicons/favicon.svg' },
5654
{ rel: 'stylesheet', href: tailwindStyleSheetUrl },
5755
].filter(Boolean)

app/routes/_app+/_auth+/verify.server.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { handleVerification as handleChangePhoneNumberVerification } from '#app/
66
import { twoFAVerificationType } from '#app/routes/_app+/settings.profile+/two-factor.tsx'
77
import { requireUserId } from '#app/utils/auth.server.ts'
88
import { prisma } from '#app/utils/db.server.ts'
9-
import { ensurePrimary } from '#app/utils/litefs.server.ts'
109
import { getDomainUrl } from '#app/utils/misc.tsx'
1110
import { redirectWithToast } from '#app/utils/toast.server.ts'
1211
import { generateTOTP, verifyTOTP } from '#app/utils/totp.server.ts'
@@ -172,10 +171,6 @@ export async function validateRequest(
172171
)
173172
}
174173

175-
// this code path could be part of a loader (GET request), so we need to make
176-
// sure we're running on primary because we're about to make writes.
177-
await ensurePrimary()
178-
179174
const { value: submissionValue } = submission
180175

181176
async function deleteVerification() {

app/routes/_app+/_auth+/verify.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export default function VerifyRoute() {
121121
inputProps={{
122122
...getInputProps(fields[codeQueryParam], { type: 'text' }),
123123
autoComplete: 'one-time-code',
124+
autoFocus: true,
124125
}}
125126
errors={fields[codeQueryParam].errors}
126127
/>

app/routes/_app+/admin+/cache_.sqlite.server.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { json, redirect, type ActionFunctionArgs } from '@remix-run/node'
2+
import { z } from 'zod'
3+
import { cache } from '#app/utils/cache.server.ts'
14
import {
25
getInstanceInfo,
36
getInternalInstanceDomain,
@@ -27,3 +30,29 @@ export async function updatePrimaryCacheValue({
2730
body: JSON.stringify({ key, cacheValue }),
2831
})
2932
}
33+
34+
export async function action({ request }: ActionFunctionArgs) {
35+
const { currentIsPrimary, primaryInstance } = await getInstanceInfo()
36+
if (!currentIsPrimary) {
37+
throw new Error(
38+
`${request.url} should only be called on the primary instance (${primaryInstance})}`,
39+
)
40+
}
41+
const token = process.env.INTERNAL_COMMAND_TOKEN
42+
const isAuthorized =
43+
request.headers.get('Authorization') === `Bearer ${token}`
44+
if (!isAuthorized) {
45+
// nah, you can't be here...
46+
return redirect('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
47+
}
48+
const { key, cacheValue } = z
49+
.object({ key: z.string(), cacheValue: z.unknown().optional() })
50+
.parse(await request.json())
51+
if (cacheValue === undefined) {
52+
await cache.delete(key)
53+
} else {
54+
// @ts-expect-error - we don't reliably know the type of cacheValue
55+
await cache.set(key, cacheValue)
56+
}
57+
return json({ success: true })
58+
}
Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1 @@
1-
import { json, redirect, type ActionFunctionArgs } from '@remix-run/node'
2-
import { z } from 'zod'
3-
import { cache } from '#app/utils/cache.server.ts'
4-
import { getInstanceInfo } from '#app/utils/litefs.server'
5-
6-
export async function action({ request }: ActionFunctionArgs) {
7-
const { currentIsPrimary, primaryInstance } = await getInstanceInfo()
8-
if (!currentIsPrimary) {
9-
throw new Error(
10-
`${request.url} should only be called on the primary instance (${primaryInstance})}`,
11-
)
12-
}
13-
const token = process.env.INTERNAL_COMMAND_TOKEN
14-
const isAuthorized =
15-
request.headers.get('Authorization') === `Bearer ${token}`
16-
if (!isAuthorized) {
17-
// nah, you can't be here...
18-
return redirect('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
19-
}
20-
const { key, cacheValue } = z
21-
.object({ key: z.string(), cacheValue: z.unknown().optional() })
22-
.parse(await request.json())
23-
if (cacheValue === undefined) {
24-
await cache.delete(key)
25-
} else {
26-
// @ts-expect-error - we don't reliably know the type of cacheValue
27-
await cache.set(key, cacheValue)
28-
}
29-
return json({ success: true })
30-
}
1+
export { action } from './cache_.sqlite.server.ts'

app/utils/db.server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const prisma = remember('prisma', () => {
1616
{ level: 'warn', emit: 'stdout' },
1717
],
1818
})
19-
client.$on('query', async e => {
19+
client.$on('query', async (e) => {
2020
if (e.duration < logThreshold) return
2121
const color =
2222
e.duration < logThreshold * 1.1
@@ -31,6 +31,6 @@ export const prisma = remember('prisma', () => {
3131
const dur = chalk[color](`${e.duration}ms`)
3232
console.info(`prisma:query - ${dur} - ${e.query}`)
3333
})
34-
client.$connect()
34+
void client.$connect()
3535
return client
3636
})

other/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ RUN echo "#!/bin/sh\nset -x\nsqlite3 \$DATABASE_URL" > /usr/local/bin/database-c
7070

7171
WORKDIR /myapp
7272

73+
# Generate random value and save it to .env file which will be loaded by dotenv
74+
RUN INTERNAL_COMMAND_TOKEN=$(openssl rand -hex 32) && \
75+
echo "INTERNAL_COMMAND_TOKEN=$INTERNAL_COMMAND_TOKEN" > .env
76+
7377
COPY --from=production-deps /myapp/node_modules /myapp/node_modules
7478
COPY --from=build /myapp/node_modules/.prisma /myapp/node_modules/.prisma
7579

0 commit comments

Comments
 (0)