{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.firstbuddha.space/sangha/contribute/schema.v1.json",
  "title": "Buddhist Calendar — community contribution file, format v1",
  "description": "What a monastery, lineage or Dharma centre already listed in the Buddhist Calendar (www.firstbuddha.space) sends to publish its events, the anniversaries of its masters (given once by Tibetan date; the calendar computes the western date every year) and daily words (quotes, instructions, reminders). One file is the whole snapshot: it replaces everything the community sent before. Rules that a schema cannot express (languages declared, the entry's own language present, at most 3 notes a date, dates in order, time zones, parts) are checked at https://www.firstbuddha.space/sangha/check. Prose: https://www.firstbuddha.space/sangha/format.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "format",
    "version",
    "source",
    "contact",
    "confirm",
    "languages",
    "entries"
  ],
  "properties": {
    "$schema": {
      "type": "string"
    },
    "format": {
      "const": "firstbuddha-sangha-contribution"
    },
    "version": {
      "const": 1
    },
    "source": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9-]{1,31}$",
      "description": "The community's id, as the editor gave it (e.g. \"knsl\")."
    },
    "community": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "name"
      ],
      "description": "For the editor.",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "website": {
          "$ref": "#/$defs/https"
        }
      }
    },
    "contact": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "name",
        "email"
      ],
      "description": "Required in a submission. Used only to reply; never published; removed on import.",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "email": {
          "type": "string",
          "maxLength": 254,
          "pattern": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"
        },
        "role": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80
        }
      }
    },
    "confirm": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "authorised",
        "rights"
      ],
      "description": "authorised: I am authorised to speak for this community. rights: we have the right to publish these texts and pictures.",
      "properties": {
        "authorised": {
          "const": true
        },
        "rights": {
          "const": true
        }
      }
    },
    "languages": {
      "type": "array",
      "minItems": 1,
      "maxItems": 8,
      "uniqueItems": true,
      "items": {
        "$ref": "#/$defs/lang"
      },
      "description": "The languages the community itself wrote, the main one first."
    },
    "machineTranslated": {
      "type": "array",
      "maxItems": 8,
      "uniqueItems": true,
      "items": {
        "$ref": "#/$defs/lang"
      },
      "description": "Languages that exist only as a machine translation the community asked for. Published only if the editor approves them."
    },
    "zone": {
      "type": "string",
      "minLength": 1,
      "description": "IANA time zone of the events' times, e.g. \"Asia/Kathmandu\". Required when any event has times."
    },
    "part": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "n",
        "of"
      ],
      "description": "One of several files of the same submission; all parts must be sent.",
      "properties": {
        "n": {
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        },
        "of": {
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        }
      }
    },
    "entries": {
      "type": "array",
      "minItems": 1,
      "maxItems": 1000,
      "items": {
        "$ref": "#/$defs/entry"
      },
      "allOf": [
        {
          "contains": {
            "type": "object",
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "event"
              }
            }
          },
          "minContains": 0,
          "maxContains": 300
        },
        {
          "contains": {
            "type": "object",
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "anniversary"
              }
            }
          },
          "minContains": 0,
          "maxContains": 100
        },
        {
          "contains": {
            "type": "object",
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "note"
              }
            }
          },
          "minContains": 0,
          "maxContains": 800
        }
      ]
    }
  },
  "$defs": {
    "lang": {
      "type": "string",
      "pattern": "^[a-z]{2,3}$",
      "description": "ISO 639 language code, lower case: \"en\", \"uk\", \"bo\"."
    },
    "text80": {
      "type": "object",
      "minProperties": 1,
      "propertyNames": {
        "$ref": "#/$defs/lang"
      },
      "additionalProperties": {
        "type": "string",
        "minLength": 1,
        "maxLength": 80
      },
      "description": "Text by language (keys from `languages` or `machineTranslated`), plain text, at most 80 characters each."
    },
    "text120": {
      "type": "object",
      "minProperties": 1,
      "propertyNames": {
        "$ref": "#/$defs/lang"
      },
      "additionalProperties": {
        "type": "string",
        "minLength": 1,
        "maxLength": 120
      },
      "description": "Text by language (keys from `languages` or `machineTranslated`), plain text, at most 120 characters each."
    },
    "text160": {
      "type": "object",
      "minProperties": 1,
      "propertyNames": {
        "$ref": "#/$defs/lang"
      },
      "additionalProperties": {
        "type": "string",
        "minLength": 1,
        "maxLength": 160
      },
      "description": "Text by language (keys from `languages` or `machineTranslated`), plain text, at most 160 characters each."
    },
    "text200": {
      "type": "object",
      "minProperties": 1,
      "propertyNames": {
        "$ref": "#/$defs/lang"
      },
      "additionalProperties": {
        "type": "string",
        "minLength": 1,
        "maxLength": 200
      },
      "description": "Text by language (keys from `languages` or `machineTranslated`), plain text, at most 200 characters each."
    },
    "text400": {
      "type": "object",
      "minProperties": 1,
      "propertyNames": {
        "$ref": "#/$defs/lang"
      },
      "additionalProperties": {
        "type": "string",
        "minLength": 1,
        "maxLength": 400
      },
      "description": "Text by language (keys from `languages` or `machineTranslated`), plain text, at most 400 characters each."
    },
    "text600": {
      "type": "object",
      "minProperties": 1,
      "propertyNames": {
        "$ref": "#/$defs/lang"
      },
      "additionalProperties": {
        "type": "string",
        "minLength": 1,
        "maxLength": 600
      },
      "description": "Text by language (keys from `languages` or `machineTranslated`), plain text, at most 600 characters each."
    },
    "text3000": {
      "type": "object",
      "minProperties": 1,
      "propertyNames": {
        "$ref": "#/$defs/lang"
      },
      "additionalProperties": {
        "type": "string",
        "minLength": 1,
        "maxLength": 3000
      },
      "description": "Text by language (keys from `languages` or `machineTranslated`), plain text, at most 3000 characters each."
    },
    "isoDate": {
      "type": "string",
      "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
      "description": "YYYY-MM-DD"
    },
    "clock": {
      "type": "string",
      "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
      "description": "HH:MM, 24-hour"
    },
    "https": {
      "type": "string",
      "maxLength": 2048,
      "pattern": "^https://[A-Za-z0-9.-]+(?::\\d{1,5})?(?:[/?#][^\\s<>\"\\\\`]*)?$"
    },
    "entryId": {
      "type": "string",
      "pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$",
      "description": "Lower case, digits and hyphens, unique in the submission; keep it the same every time the file is sent."
    },
    "tibetanRule": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "month",
        "day"
      ],
      "description": "A Tibetan date (Phugpa reckoning), never a western one. leapMonth matters only in a year when that month is doubled: first (default), second or both halves.",
      "properties": {
        "month": {
          "type": "integer",
          "minimum": 1,
          "maximum": 12
        },
        "day": {
          "type": "integer",
          "minimum": 1,
          "maximum": 30
        },
        "leapMonth": {
          "enum": [
            "first",
            "second",
            "both"
          ]
        }
      }
    },
    "image": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "credit",
        "rightsConfirmed"
      ],
      "description": "One picture per entry: a file attached to the email, or a picture the calendar already publishes for this community.",
      "properties": {
        "file": {
          "type": "string",
          "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,100}\\.([Jj][Pp][Ee]?[Gg]|[Pp][Nn][Gg]|[Ww][Ee][Bb][Pp]|[Hh][Ee][Ii][Cc])$"
        },
        "url": {
          "type": "string",
          "pattern": "^https://www\\.firstbuddha\\.space/media/sangha/[a-z][a-z0-9-]{1,31}/[0-9a-f]{16}\\.jpg$"
        },
        "credit": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "rightsConfirmed": {
          "const": true
        },
        "alt": {
          "$ref": "#/$defs/text200"
        }
      },
      "oneOf": [
        {
          "required": [
            "file"
          ]
        },
        {
          "required": [
            "url"
          ]
        }
      ]
    },
    "entry": {
      "oneOf": [
        {
          "$ref": "#/$defs/event"
        },
        {
          "$ref": "#/$defs/anniversary"
        },
        {
          "$ref": "#/$defs/note"
        }
      ]
    },
    "event": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "type",
        "id",
        "kind",
        "title",
        "allDay",
        "startDate"
      ],
      "properties": {
        "type": {
          "const": "event"
        },
        "id": {
          "$ref": "#/$defs/entryId"
        },
        "lang": {
          "$ref": "#/$defs/lang"
        },
        "kind": {
          "enum": [
            "teaching",
            "ritual",
            "monastic",
            "course",
            "class",
            "community",
            "other"
          ]
        },
        "title": {
          "$ref": "#/$defs/text160"
        },
        "subtitle": {
          "$ref": "#/$defs/text200"
        },
        "summary": {
          "$ref": "#/$defs/text400"
        },
        "description": {
          "$ref": "#/$defs/text3000"
        },
        "location": {
          "$ref": "#/$defs/text200"
        },
        "zone": {
          "type": "string",
          "minLength": 1
        },
        "allDay": {
          "type": "boolean"
        },
        "startDate": {
          "$ref": "#/$defs/isoDate"
        },
        "endDate": {
          "$ref": "#/$defs/isoDate"
        },
        "startTime": {
          "$ref": "#/$defs/clock"
        },
        "endTime": {
          "$ref": "#/$defs/clock"
        },
        "daily": {
          "type": "boolean",
          "description": "Only for a timed event over several dates: true = one session each date, false = one continuous span."
        },
        "url": {
          "$ref": "#/$defs/https"
        },
        "videoUrl": {
          "$ref": "#/$defs/https"
        },
        "registration": {
          "enum": [
            "required",
            "none"
          ]
        },
        "status": {
          "const": "cancelled"
        },
        "image": {
          "$ref": "#/$defs/image"
        }
      },
      "if": {
        "properties": {
          "allDay": {
            "const": true
          }
        }
      },
      "then": {
        "not": {
          "anyOf": [
            {
              "required": [
                "startTime"
              ]
            },
            {
              "required": [
                "endTime"
              ]
            },
            {
              "required": [
                "daily"
              ]
            }
          ]
        }
      },
      "else": {
        "required": [
          "startTime"
        ]
      }
    },
    "anniversary": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "type",
        "id",
        "title",
        "tibetan"
      ],
      "description": "Defined once by Tibetan date; recurs every year. Never a western date or a year.",
      "properties": {
        "type": {
          "const": "anniversary"
        },
        "id": {
          "$ref": "#/$defs/entryId"
        },
        "lang": {
          "$ref": "#/$defs/lang"
        },
        "title": {
          "$ref": "#/$defs/text160"
        },
        "subtitle": {
          "$ref": "#/$defs/text200"
        },
        "summary": {
          "$ref": "#/$defs/text400"
        },
        "tibetan": {
          "$ref": "#/$defs/tibetanRule"
        },
        "url": {
          "$ref": "#/$defs/https"
        },
        "image": {
          "$ref": "#/$defs/image"
        }
      }
    },
    "note": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "type",
        "id",
        "kind",
        "text"
      ],
      "description": "A quote, instruction or reminder: once on a date, or every year on a Tibetan date.",
      "properties": {
        "type": {
          "const": "note"
        },
        "id": {
          "$ref": "#/$defs/entryId"
        },
        "lang": {
          "$ref": "#/$defs/lang"
        },
        "kind": {
          "enum": [
            "quote",
            "instruction",
            "reminder"
          ]
        },
        "title": {
          "$ref": "#/$defs/text120"
        },
        "text": {
          "$ref": "#/$defs/text600"
        },
        "attribution": {
          "type": "object",
          "additionalProperties": false,
          "minProperties": 1,
          "properties": {
            "author": {
              "$ref": "#/$defs/text120"
            },
            "work": {
              "$ref": "#/$defs/text200"
            },
            "ref": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "translator": {
              "$ref": "#/$defs/text120"
            }
          }
        },
        "date": {
          "$ref": "#/$defs/isoDate"
        },
        "tibetan": {
          "$ref": "#/$defs/tibetanRule"
        },
        "url": {
          "$ref": "#/$defs/https"
        },
        "image": {
          "$ref": "#/$defs/image"
        }
      },
      "oneOf": [
        {
          "required": [
            "date"
          ]
        },
        {
          "required": [
            "tibetan"
          ]
        }
      ],
      "if": {
        "properties": {
          "kind": {
            "const": "quote"
          }
        }
      },
      "then": {
        "required": [
          "attribution"
        ],
        "properties": {
          "attribution": {
            "required": [
              "author"
            ]
          }
        }
      }
    }
  }
}
