Compare commits

..

4 commits
prod ... main

Author SHA1 Message Date
D. Scott Boggs 5aea1d3946 Repo is deployment dir
Some checks failed
API Checks / clippy (push) Has been cancelled
Frontend Checks / lint (push) Has been cancelled
Frontend Checks / typecheck (push) Has been cancelled
Deploy API / Deploy to Fly.io (push) Has been cancelled
Deploy Frontend / Deploy to Vercel (push) Has been cancelled
Rather than putting the source repo in a build dir and having a mount dir next to that, the
source repo itself is the deployment dir, with the mount dir inside of it. We do not want
to commit the contents of the database to the repo obviously
2025-05-08 08:54:57 -04:00
D. Scott Boggs 13c5193344 Fix Next config to match example Dockerfile expectations 2025-05-08 08:54:30 -04:00
D. Scott Boggs e459bb20ee UID/GID 1000 was in use 2025-05-08 08:54:30 -04:00
D. Scott Boggs 4b7ec853b1 Password files must be trimmed of newlines 2025-05-08 08:54:30 -04:00
13 changed files with 19 additions and 99 deletions

2
.gitignore vendored
View file

@ -1,6 +1,6 @@
/graphics /graphics
.DS_Store .DS_Store
**/*.pw
**/*.secret **/*.secret
**/*.pw
mounts/ mounts/

View file

@ -16,13 +16,7 @@ RUN --mount=type=cache,target=/usr/local/cargo,from=rust:latest,source=/usr/loca
cargo build --release --features $adaptor && mv ./target/release/crabfit-api ./api cargo build --release --features $adaptor && mv ./target/release/crabfit-api ./api
# Runtime image # Runtime image
FROM debian:bookworm-slim FROM debian:bullseye-slim
# install libssl3
RUN apt-get update &&\
apt-get install -yq libssl3 &&\
apt-get clean &&\
rm -rf /var/cache/apt/lists/*
# Run as "app" user # Run as "app" user
RUN useradd -ms /bin/bash app RUN useradd -ms /bin/bash app

View file

@ -1,60 +0,0 @@
services:
crabfit-api:
build:
context: ./api
# args:
# adaptor: sql-adaptor (default) | memory-adaptor | datastore-adaptor
# # datastore is for Google Datastore
secrets:
- crabfit-database-password
- crabfit-cron-key
environment:
DATABASE_PASSWORD_FILE: /run/secrets/crabfit-database-password
DATABASE_URL: postgresql://crabfit@crabfit-database:5432/crabfit
FRONTEND_URL: https://availability.techwork.zone
CRON_KEY_FILE: /run/secrets/crabfit-cron-key
labels:
traefik.enable: true
traefik.http.routers.crabfit-api.rule: Host(`api.a10y.techwork.zone`)
traefik.http.routers.crabfit-api.tls: true
traefik.http.routers.crabfit-api.tls.certresolver: letsencrypt_standalone
networks:
- crabfit
- public
crabfit-database:
image: postgres:17
secrets: [ 'crabfit-database-password' ]
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/crabfit-database-password
POSTGRES_USER: crabfit
POSTGRES_DB: crabfit
volumes:
- ./mounts/database:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready"]
interval: 30s
timeout: 20s
retries: 3
networks: [ crabfit ]
crabfit-frontend:
build:
context: ./frontend
labels:
traefik.enable: true
traefik.http.routers.crabfit-frontend.rule: Host(`a10y.techwork.zone`) || Host(`availability.techwork.zone`)
traefik.http.routers.crabfit-frontend.tls: true
traefik.http.routers.crabfit-frontend.tls.certresolver: letsencrypt_standalone
networks: [ public ]
networks:
crabfit:
internal: true
secrets:
crabfit-database-password:
file: ./postgres.pw
crabfit-cron-key:
file: ./cron.secret

View file

@ -1,5 +1,4 @@
NEXT_PUBLIC_API_URL="https://api.a10y.techwork.zone" NEXT_PUBLIC_API_URL="http://127.0.0.1:3000"
NEXT_PUBLIC_HOSTNAME=availability.techwork.zone
# Google auth for calendar syncing, feature will be disabled if these aren't set # Google auth for calendar syncing, feature will be disabled if these aren't set
# NEXT_PUBLIC_GOOGLE_CLIENT_ID="" # NEXT_PUBLIC_GOOGLE_CLIENT_ID=""

View file

@ -15,6 +15,7 @@
"@giraugh/tools": "^1.6.0", "@giraugh/tools": "^1.6.0",
"@js-temporal/polyfill": "^0.4.4", "@js-temporal/polyfill": "^0.4.4",
"@microsoft/microsoft-graph-client": "^3.0.5", "@microsoft/microsoft-graph-client": "^3.0.5",
"@vercel/analytics": "^1.0.1",
"accept-language": "^3.0.18", "accept-language": "^3.0.18",
"chroma.ts": "^1.0.10", "chroma.ts": "^1.0.10",
"hue-map": "^1.0.0", "hue-map": "^1.0.0",
@ -46,10 +47,5 @@
"sass": "^1.63.4", "sass": "^1.63.4",
"typescript": "^5.1.3", "typescript": "^5.1.3",
"typescript-plugin-css-modules": "^5.0.1" "typescript-plugin-css-modules": "^5.0.1"
},
"prettier": {
"semi": false,
"arrowParens": "avoid",
"singleQuote": true
} }
} }

View file

@ -12,7 +12,6 @@ import { makeClass, relativeTimeFormat } from '/src/utils'
import EventAvailabilities from './EventAvailabilities' import EventAvailabilities from './EventAvailabilities'
import styles from './page.module.scss' import styles from './page.module.scss'
import appLink from '/src/utils/appLink'
interface PageProps { interface PageProps {
params: { id: string } params: { id: string }
@ -50,10 +49,10 @@ const Page = async ({ params }: PageProps) => {
>{t('common:created', { date: relativeTimeFormat(Temporal.Instant.fromEpochSeconds(event.created_at), i18n.language) })}</span> >{t('common:created', { date: relativeTimeFormat(Temporal.Instant.fromEpochSeconds(event.created_at), i18n.language) })}</span>
<Copyable className={styles.info}> <Copyable className={styles.info}>
{appLink(event.id)} {`https://crab.fit/${event.id}`}
</Copyable> </Copyable>
<p className={makeClass(styles.info, styles.noPrint)}> <p className={makeClass(styles.info, styles.noPrint)}>
<Trans i18nKey="event:nav.shareinfo" t={t} i18n={i18n}>_<a href={`mailto:?subject=${encodeURIComponent(t('event:nav.email_subject', { event_name: event.name }))}&body=${encodeURIComponent(`${t('event:nav.email_body')} ${appLink(event.id)}`)}`}>_</a>_</Trans> <Trans i18nKey="event:nav.shareinfo" t={t} i18n={i18n}>_<a href={`mailto:?subject=${encodeURIComponent(t('event:nav.email_subject', { event_name: event.name }))}&body=${encodeURIComponent(`${t('event:nav.email_body')} https://crab.fit/${event.id}`)}`}>_</a>_</Trans>
</p> </p>
</Content> </Content>
</Suspense> </Suspense>

View file

@ -1,5 +1,6 @@
import { Metadata } from 'next' import { Metadata } from 'next'
import { Karla } from 'next/font/google' import { Karla } from 'next/font/google'
import { Analytics } from '@vercel/analytics/react'
import Egg from '/src/components/Egg/Egg' import Egg from '/src/components/Egg/Egg'
import Settings from '/src/components/Settings/Settings' import Settings from '/src/components/Settings/Settings'
@ -8,12 +9,11 @@ import { fallbackLng } from '/src/i18n/options'
import { useTranslation } from '/src/i18n/server' import { useTranslation } from '/src/i18n/server'
import './global.css' import './global.css'
import appLink from '../utils/appLink'
const karla = Karla({ subsets: ['latin'] }) const karla = Karla({ subsets: ['latin'] })
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL(appLink('')), metadataBase: new URL('https://crab.fit'),
title: { title: {
absolute: 'Crab Fit', absolute: 'Crab Fit',
template: '%s - Crab Fit', template: '%s - Crab Fit',
@ -44,6 +44,7 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
{children} {children}
<Analytics />
</body> </body>
</html> </html>
} }

View file

@ -40,10 +40,10 @@ const Page = async () => {
<Video /> <Video />
{/* <DownloadButtons /> */} <DownloadButtons />
<P><Trans i18nKey="about.content.p3" t={t} i18n={i18n}>_<a href="https://bengrant.dev" target="_blank" rel="noreferrer noopener author">_</a><a href="https://techwork.zone" target="_blank" rel="noreferrer noopener">_</a>_</Trans></P> <P><Trans i18nKey="about.content.p3" t={t} i18n={i18n}>_<a href="https://bengrant.dev" target="_blank" rel="noreferrer noopener author">_</a>_</Trans></P>
<P><Trans i18nKey="about.content.p4" t={t} i18n={i18n}>_<a href="https://git.techwork.zone/scott/crabfit" target="_blank" rel="noreferrer noopener">_</a>_<Link href="/privacy" rel="license">_</Link>_</Trans></P> <P><Trans i18nKey="about.content.p4" t={t} i18n={i18n}>_<a href="https://github.com/GRA0007/crab.fit" target="_blank" rel="noreferrer noopener">_</a>_<Link href="/privacy" rel="license">_</Link>_</Trans></P>
<P>{t('about.content.p6')}</P> <P>{t('about.content.p6')}</P>
<P>{t('about.content.p5')}</P> <P>{t('about.content.p5')}</P>
</Content> </Content>

View file

@ -5,7 +5,6 @@ import { EventResponse } from '/src/config/api'
import { useTranslation } from '/src/i18n/client' import { useTranslation } from '/src/i18n/client'
import styles from './EventInfo.module.scss' import styles from './EventInfo.module.scss'
import appLink from '/src/utils/appLink'
interface EventInfoProps { interface EventInfoProps {
event: EventResponse event: EventResponse
@ -17,10 +16,10 @@ const EventInfo = ({ event }: EventInfoProps) => {
return <div className={styles.wrapper}> return <div className={styles.wrapper}>
<h2>{event.name}</h2> <h2>{event.name}</h2>
<Copyable className={styles.info}> <Copyable className={styles.info}>
{appLink(event.id)} {`https://crab.fit/${event.id}`}
</Copyable> </Copyable>
<p className={styles.info}> <p className={styles.info}>
<Trans i18nKey="event:nav.shareinfo_alt" t={t} i18n={i18n}>_<a href={`mailto:?subject=${encodeURIComponent(t('nav.email_subject', { event_name: event.name }))}&body=${encodeURIComponent(`${t('nav.email_body')} ${appLink(event.id)}`)}`} target="_blank">_</a>_</Trans> <Trans i18nKey="event:nav.shareinfo_alt" t={t} i18n={i18n}>_<a href={`mailto:?subject=${encodeURIComponent(t('nav.email_subject', { event_name: event.name }))}&body=${encodeURIComponent(`${t('nav.email_body')} https://crab.fit/${event.id}`)}`} target="_blank">_</a>_</Trans>
</p> </p>
</div> </div>
} }

View file

@ -6,7 +6,6 @@ import logo from '/src/res/logo.svg'
import { makeClass } from '/src/utils' import { makeClass } from '/src/utils'
import styles from './Header.module.scss' import styles from './Header.module.scss'
import appLink from '/src/utils/appLink'
const samuraiBob = localFont({ const samuraiBob = localFont({
src: './samuraibob.woff2', src: './samuraibob.woff2',
@ -30,11 +29,11 @@ const Header = async ({ isFull, isSmall }: HeaderProps) => {
{isFull ? <> {isFull ? <>
{!isSmall && <img className={styles.bigLogo} src={logo.src} height={512} width={512} alt="" />} {!isSmall && <img className={styles.bigLogo} src={logo.src} height={512} width={512} alt="" />}
<span className={makeClass(styles.subtitle, samuraiBob.className, !/^[A-Za-z ]+$/.test(t('home:create')) && styles.hasAltChars)}>{t('home:create')}</span> <span className={makeClass(styles.subtitle, samuraiBob.className, !/^[A-Za-z ]+$/.test(t('home:create')) && styles.hasAltChars)}>{t('home:create')}</span>
<h1 className={makeClass(styles.bigTitle, molot.className)}>MEETING</h1> <h1 className={makeClass(styles.bigTitle, molot.className)}>CRAB FIT</h1>
</> : <Link href="/" className={styles.link}> </> : <Link href="/" className={styles.link}>
<div className={styles.top}> <div className={styles.top}>
<img className={styles.logo} src={logo.src} height={512} width={512} alt="" /> <img className={styles.logo} src={logo.src} height={512} width={512} alt="" />
<span className={makeClass(styles.title, molot.className)}>MEETING</span> <span className={makeClass(styles.title, molot.className)}>CRAB FIT</span>
</div> </div>
<span className={styles.tagline}>{t('common:tagline')}</span> <span className={styles.tagline}>{t('common:tagline')}</span>
</Link>} </Link>}

View file

@ -4,7 +4,7 @@
"cta": "Create your own Crab Fit!", "cta": "Create your own Crab Fit!",
"created": "Created {{date}}", "created": "Created {{date}}",
"donate": { "donate": {
"info": "Thank you for using Crab Fit. If you like it, consider donating to the upstream author.", "info": "Thank you for using Crab Fit. If you like it, consider donating.",
"button": "Donate", "button": "Donate",
"title": "Every amount counts :)" "title": "Every amount counts :)"
}, },

View file

@ -45,8 +45,8 @@
"events": "Events created", "events": "Events created",
"availabilities": "Availabilities entered", "availabilities": "Availabilities entered",
"content": { "content": {
"p1": "This service is a soft fork of Crab Fit. Crab Fit helps you fit your event around everyone's schedules. Simply create an event above and send the link to everyone that is participating. Results update live and you will be able to see a heat-map of when everyone is free.<1/><2>Learn more about how to Crab Fit</2>.", "p1": "Crab Fit helps you fit your event around everyone's schedules. Simply create an event above and send the link to everyone that is participating. Results update live and you will be able to see a heat-map of when everyone is free.<1/><2>Learn more about how to Crab Fit</2>.",
"p3": "Created by <1>Ben Grant</1> and hosted by the <2>Tech Workers' Syndicate</2>, Crab Fit is the modern-day solution to your group event planning debates.", "p3": "Created by <1>Ben Grant</1>, Crab Fit is the modern-day solution to your group event planning debates.",
"p4": "The code for Crab Fit is open source, if you find any issues or want to contribute, you can visit the <1>repository</1>. By using Crab Fit you agree to the <3>privacy policy</3>.", "p4": "The code for Crab Fit is open source, if you find any issues or want to contribute, you can visit the <1>repository</1>. By using Crab Fit you agree to the <3>privacy policy</3>.",
"p6": "To protect your privacy, events are deleted after 3 months of inactivity, and all passwords are securely hashed.", "p6": "To protect your privacy, events are deleted after 3 months of inactivity, and all passwords are securely hashed.",
"p5": "Consider donating below if it helped you out so Crab Fit can stay free for everyone. 🦀" "p5": "Consider donating below if it helped you out so Crab Fit can stay free for everyone. 🦀"

View file

@ -1,7 +0,0 @@
export default function appLink(path: string) {
const proto = process?.env?.NODE_ENV === 'production' ? 'https' : 'http'
const host =
process?.env?.NEXT_PUBLIC_HOSTNAME ??
(process?.env?.NODE_ENV === 'production' ? 'crab.fit' : 'localhost:3000')
return `${proto}://${host}/${path}`
}