Strautomator
Strava 활동을 자동화해 주는 Strautomator는 IFTTT와 비슷하지만 Strava 전용이다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Automates a private Fantasy Premier League league owner's group communications: standings, results and the money pot sent by email or WhatsApp
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Automates a private Fantasy Premier League owner's group communications — standings,
results, money pot — instead of them posting by hand.
The app never sends on the owner's behalf. It prepares a draft; the owner writes, composes
and taps. League members never log in.README 전문 보기 ↗Automates a private Fantasy Premier League owner's group communications — standings, results, money pot — instead of them posting by hand.
The app never sends on the owner's behalf. It prepares a draft; the owner writes, composes and taps. League members never log in.
MIT licensed — see LICENSE. It is a personal project rather than a product:
there is no support, no release cadence, and no expectation that it runs anywhere but the
one deployment it was written for. The fixtures under src/lib/fpl/recorded/ are real FPL
API responses with the identities replaced by invented ones; see SECURITY.md.
Needs Node 24 and a local Postgres.
cp .env.example .env # then fill in your local Postgres password
psql -U postgres -c "CREATE DATABASE fphelp_dev;"
npm install
npm run db:migrate
npm run dev
Without AUTH_RESEND_KEY, sign-in links are printed to the server console instead of
emailed, so local development works before a domain is verified.
Run the dev server once before reaching for tsc. On a fresh clone npx tsc --noEmit
fails with TS2304: Cannot find name 'LayoutProps', which looks like broken code and is
not. Next generates the route types — LayoutProps, PageProps — into .next/types,
which tsconfig.json includes; they do not exist until next dev or next build has run
once. npm test and npm run lint need none of this and pass on a bare clone.
You will also need a users row to sign in at all — access is allowlist-based and there is
no self-signup. Set BOOTSTRAP_OWNER_EMAIL and run npm run db:bootstrap.
npm test # Vitest — the pure computation layer, where the silent-wrong-number bugs live
npm run e2e:db # once, and again whenever migrations change
npm run e2e # Playwright — pages, Server Actions, guards
npm run e2e:screens # screenshots of every page in both themes, for looking at
The end-to-end suite truncates every table, so it refuses any database that is not named
fphelp_e2e and local. It never calls the real FPL API.
Railway, project fphelp. Pushing to main deploys — there is no separate step, so
work on a branch and merge deliberately.
| Service | What it is |
|---|---|
fphelp-app | The web app; the only one with a public domain |
cron-capture-history | Cron only. Runs npm run job:capture every 40 minutes |
railway.json sets a preDeployCommand that runs migrations and the owner bootstrap, so a
schema change reaches production on deploy.
Only from inside the container. railway run does not work and fails confusingly: it
injects the production environment into a process on your machine, so DATABASE_URL
resolves to postgres.railway.internal, which exists only on Railway's private network —
you get ENOTFOUND after everything looked correct.
railway ssh --service fphelp-app
There is no psql in the image; use Node and pg, which are already there.
Setup can do both, so this is for the cases it can't reach:
removeOwnerAction refuses it, so whoever set
the league up and does not want to appear as one of its owners has to leave from here —
or ask a co-owner to remove them.Ownership is two rows. users is what src/auth.ts allowlists for sign-in; league_users
is what every page and action checks. Adding needs both; removing deletes only the
second, because messages.created_by and deliveries.sent_by are set null and
deleting the account would erase who sent past digests.
Inside railway ssh --service fphelp-app, first see who is there:
node -e 'const{Client}=require("pg");const c=new Client({connectionString:process.env.DATABASE_URL,ssl:{rejectUnauthorized:false}});c.connect().then(()=>c.query("select u.email, u.email_verified is not null as has_signed_in from league_users lu join users u on u.id=lu.user_id order by lu.created_at")).then(r=>r.rows.forEach(x=>console.log(x.email, x.has_signed_in?"(has signed in)":"(never signed in)"))).then(()=>c.end()).catch(e=>{console.error(e.message);process.exit(1)})'
Add one — idempotent, so re-running it is safe:
node -e 'const{Client}=require("pg");const c=new Client({connectionString:process.env.DATABASE_URL,ssl:{rejectUnauthorized:false}});const email="[email protected]";c.connect().then(()=>c.query("insert into users (email) values ($1) on conflict (email) do nothing",[email])).then(()=>c.query("insert into league_users (league_id, user_id) select l.id, u.id from leagues l, users u where u.email=$1 on conflict do nothing",[email])).then(r=>console.log(r.rowCount?"added":"already an owner")).then(()=>c.end()).catch(e=>{console.error(e.message);process.exit(1)})'
Remove one — drops the membership, keeps the account and its authorship:
node -e 'const{Client}=require("pg");const c=new Client({connectionString:process.env.DATABASE_URL,ssl:{rejectUnauthorized:false}});const email="[email protected]";c.connect().then(()=>c.query("delete from league_users where user_id=(select id from users where email=$1)",[email])).then(r=>console.log("removed",r.rowCount,"membership(s)")).then(()=>c.end()).catch(e=>{console.error(e.message);process.exit(1)})'
BOOTSTRAP_OWNER_EMAIL undoes a removal on the next deploy. preDeployCommand runs
db:bootstrap every time, which re-adds whatever address that variable names — so removing
yourself while it still points at you means reappearing in the owners list the next time
anything ships, with no explanation for the people watching.
Point it at an owner who should always exist. Then it stops being a footgun and becomes their break-glass: if they lose access, a redeploy restores it.
A session can end up belonging to something that is not a person. Sign-in links are
single-use, and any app that renders a link preview will fetch the URL to build it —
which verifies the token, consumes it, and receives the session cookie. The owner's own tap
then arrives second and fails with error=Verification.
This is not hypothetical: on 2026-08-17 three sessions were created by
WhatsApp/2.23.20.0, 3–8 seconds ahead of the owner's iPhone each time, because the link
was being forwarded through WhatsApp to get it from a desktop inbox onto the phone. Every
chat app does this — iMessage, Signal, Telegram and Slack included. Open sign-in links on
the device that received the email.
The stranded sessions stay valid for 30 days, so they are worth clearing. Inside
railway ssh --service fphelp-app, first look:
node -e 'const{Client}=require("pg");const c=new Client({connectionString:process.env.DATABASE_URL,ssl:{rejectUnauthorized:false}});c.connect().then(()=>c.query("select s.expires from sessions s join users u on u.id=s.user_id where u.email=$1",["[email protected]"])).then(r=>console.log(r.rowCount,"sessions")).then(()=>c.end()).catch(e=>{console.error(e.message);process.exit(1)})'
then delete:
node -e 'const{Client}=require("pg");const c=new Client({connectionString:process.env.DATABASE_URL,ssl:{rejectUnauthorized:false}});c.connect().then(()=>c.query("delete from sessions where user_id=(select id from users where email=$1)",["[email protected]"])).then(r=>console.log("deleted",r.rowCount)).then(()=>c.end()).catch(e=>{console.error(e.message);process.exit(1)})'
Single quotes outside and double inside, so nothing needs escaping; one line each, because they are promise chains rather than statements.
This removes every session for that address, including your own — so either run it before signing in, or expect to sign in again afterwards. That is the right trade: you cannot tell your session from a fetcher's by looking, and signing in again is cheap.
Expired rows in verification_tokens need no attention. Auth.js checks expiry, so they
cannot be used; they are only clutter.
railway logs --service fphelp-app # application output
railway logs --service fphelp-app --http --since 1h # requests, with client user-agent
railway logs --service cron-capture-history # the capture job, not the web app
The HTTP logs carry clientUa, which is what identified WhatsApp above. Reach for them
whenever a request seems to have happened without anyone making it.
npm run db:clear-digest -- --gw N — deletes one gameweek's digest, messages and
deliveries. Dry run without --confirm. Manual only, never a deploy hook — it removes
the owner's own writing, which cannot be recovered from the API.railway redeploy --service fphelp-app — new environment variables need a redeploy to
reach a running container, and a missing value looks exactly like a bad one.