All files / Bandstand/src/lib band-fields.ts

67.16% Statements 45/67
50% Branches 2/4
33.33% Functions 1/3
67.16% Lines 45/67

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 771x 1x   1x                   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x   1x                       1x 1x 1x 1x 1x     1x 1x            
import {z} from 'zod'
import {isHttpUrl} from './url'
 
export function isTimeZone(tz: string) {
  try {
    new Intl.DateTimeFormat('en-US', {timeZone: tz})
    return true
  } catch {
    return false
  }
}
 
/** What a band's admins can change about it. */
export const BandFields = z.object({
  name: z.string().trim().min(1).max(80).optional(),
  appName: z.string().trim().min(1).max(40).optional(),
  timezone: z
    .string()
    .trim()
    .refine(isTimeZone, 'Unknown time zone')
    .optional(),
  chatUrl: z
    .string()
    .trim()
    .transform((v) => v || null)
    .refine((v) => v === null || isHttpUrl(v), 'Must be an http(s) URL')
    .nullable()
    .optional(),
  tributeTo: z
    .string()
    .trim()
    .max(80)
    .transform((v) => v || null)
    .nullable()
    .optional(),
  voteThreshold: z.number().int().min(1).max(50).optional(),
  scheduling: z.boolean().optional(),
})
 
/** "members.example.com" — lowercase, no scheme, port or path. */
export const Host = z
  .string()
  .trim()
  .toLowerCase()
  .transform((v) => v.replace(/^https?:\/\//, '').replace(/[/:].*$/, ''))
  .refine(
    (v) => /^(?=.{1,253}$)([a-z0-9-]{1,63}\.)*[a-z0-9-]{1,63}$/.test(v),
    'Not a web address',
  )
 
export function slugify(name: string) {
  return (
    name
      .toLowerCase()
      .normalize('NFKD')
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/^-|-$/g, '')
      .slice(0, 40) || 'band'
  )
}
 
/** The label for a band's chat link, from where it points. */
export function chatLabel(url: string) {
  const host = (() => {
    try {
      return new URL(url).hostname
    } catch {
      return ''
    }
  })()
  if (/discord\./.test(host)) return 'Band Discord'
  if (/whatsapp\./.test(host)) return 'Band WhatsApp'
  if (/slack\./.test(host)) return 'Band Slack'
  if (/groupme\./.test(host)) return 'Band GroupMe'
  return 'Band chat'
}