Build and Push Docker Image / build-and-push (push) Successful in 6m47s
- Admin: sign-up Open / Invite only / Closed with single-use invite links - Present mode for scripts (auto-scroll) and outlines (step through, pace vs planned minutes) - Series: group studies on the dashboard, filter by series, calendar file for a study's sessions - Lesson handout: printable page per session (passage, outline, questions, writing lines) - Save a chunk's notes as your own study template (synced) - Voice dictation for notes and scripts (browser speech recognition) - Reading progress: map of the 66 books and a calendar of days read, synced and merged across devices - Export highlights and bookmarks as Markdown or Word, grouped by named colour - Verse of the day push to the ntfy topic (needs IQBIBLE_API_KEY) - Sharing: co-teacher editing via single-use invite links; read-only links can expire and take a passcode Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
26 lines
1.5 KiB
JavaScript
26 lines
1.5 KiB
JavaScript
// Who may create an account. The administrator picks one of three settings:
|
|
// open anyone who can reach the server (the original behaviour)
|
|
// invite only someone with an unused invite code from the administrator
|
|
// closed nobody new
|
|
// The very first account can always be made, so a fresh install can't lock itself out.
|
|
import { randomBytes } from 'crypto';
|
|
|
|
export const SIGNUP_MODES = ['open', 'invite', 'closed'];
|
|
export const isSignupMode = (m) => SIGNUP_MODES.includes(m);
|
|
|
|
// -> { ok: true } or { ok: false, status, error }. `inviteOk` says whether the code given is a valid unused one.
|
|
export function checkSignup({ mode, userCount, inviteOk }) {
|
|
if (userCount === 0 || mode === 'open') return { ok: true };
|
|
if (mode === 'closed') return { ok: false, status: 403, error: 'Sign-up is closed. Ask the administrator for an account.' };
|
|
if (!inviteOk) return { ok: false, status: 403, error: 'Sign-up needs an invite code from the administrator.' };
|
|
return { ok: true };
|
|
}
|
|
|
|
// An easy-to-read invite code (no look-alike characters), e.g. K7QP-4MXD-9TRW.
|
|
const ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
|
|
export function generateInviteCode() {
|
|
const chars = Array.from(randomBytes(12), (b) => ALPHABET[b % ALPHABET.length]).join('');
|
|
return `${chars.slice(0, 4)}-${chars.slice(4, 8)}-${chars.slice(8, 12)}`;
|
|
}
|
|
export const normalizeInviteCode = (code) => String(code ?? '').toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/^(.{4})(.{4})(.{4})$/, '$1-$2-$3');
|