Compare commits
No commits in common. "main" and "0.2.2" have entirely different histories.
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,7 +1,6 @@
|
||||
.DS_Store
|
||||
.terraform
|
||||
node_modules
|
||||
/api/deploy
|
||||
/web/dist
|
||||
/dist
|
||||
|
||||
@ -26,4 +25,3 @@ pnpm-debug.log*
|
||||
.*.sw?
|
||||
|
||||
api/hff_entry_forms_api
|
||||
api/test/runner
|
||||
|
24
.lvimrc
24
.lvimrc
@ -1,24 +0,0 @@
|
||||
" The below configuration depends on global versions of some of these tools.
|
||||
" All tooling can be installed with the following:
|
||||
"
|
||||
" asdf local nodejs latest
|
||||
" npm install -g typescript prettier eslint vue-tsc
|
||||
"
|
||||
let g:ale_fixers = {
|
||||
\ '*': ['remove_trailing_lines', 'trim_whitespace'],
|
||||
\ 'javascript': ['eslint', 'prettier'],
|
||||
\ 'typescript': ['prettier'],
|
||||
\ 'vue': ['prettier'],
|
||||
\}
|
||||
|
||||
let g:ale_linters = {
|
||||
\ 'markdown': [],
|
||||
\ 'html': ['HTMLHint', 'proselint', 'write-good'],
|
||||
\ 'rst': [],
|
||||
\ 'typescript': ['typescript', 'eslint'],
|
||||
\ 'javascript': ['eslint'],
|
||||
\ 'vue': ['vuetsc', 'prettier'],
|
||||
\}
|
||||
|
||||
let g:ale_fix_on_save = 1
|
||||
"let g:ale_linters_explicit = 1
|
@ -1,2 +0,0 @@
|
||||
opentofu 1.8.1
|
||||
nim 1.6.20
|
31
Makefile
31
Makefile
@ -1,35 +1,32 @@
|
||||
VERSION:=$(shell git describe --always)
|
||||
TARGET_ENV ?= dev
|
||||
|
||||
build: api-image dist/hff-entry-forms-web.${TARGET_ENV}.tar.gz
|
||||
build: dist/hff-entry-forms-api.tar.gz dist/hff-entry-forms-web.tar.gz
|
||||
|
||||
clean:
|
||||
-rm -r dist
|
||||
make -C api clean
|
||||
make -C web clean
|
||||
|
||||
test:
|
||||
(cd api && nimble unittest)
|
||||
-rm -r web/dist
|
||||
-docker container prune
|
||||
-docker image prune
|
||||
|
||||
update-version:
|
||||
operations/update-version.sh
|
||||
|
||||
api-image:
|
||||
make -C api build-image
|
||||
|
||||
dist/hff-entry-forms-web-${VERSION}.${TARGET_ENV}.tar.gz:
|
||||
dist/hff-entry-forms-web.tar.gz:
|
||||
-mkdir dist
|
||||
TARGET_ENV=${TARGET_ENV} make -C web build
|
||||
tar czf dist/hff-entry-forms-web-${VERSION}.${TARGET_ENV}.tar.gz -C web/dist .
|
||||
TARGET_ENV=$(TARGET_ENV) make -C web build
|
||||
tar czf dist/hff-entry-forms-web-${VERSION}.tar.gz -C web/dist .
|
||||
cp dist/hff-entry-forms-web-${VERSION}.tar.gz dist/hff-entry-forms-web.tar.gz
|
||||
|
||||
deploy-api:
|
||||
TARGET_ENV=${TARGET_ENV} make -C api build-image push-image publish
|
||||
make -C api build-image push-image
|
||||
cd operations/terraform && terraform apply -target module.${TARGET_ENV}_env.aws_ecs_task_definition.hff_entry_forms_api -target module.${TARGET_ENV}_env.aws_ecs_service.hff_entry_forms_api
|
||||
|
||||
deploy-web: dist/hff-entry-forms-web-${VERSION}.${TARGET_ENV}.tar.gz
|
||||
deploy-web: dist/hff-entry-forms-web.tar.gz
|
||||
mkdir -p temp-deploy/hff-entry-forms-web-${VERSION}
|
||||
tar xzf dist/hff-entry-forms-web-${VERSION}.${TARGET_ENV}.tar.gz -C temp-deploy/hff-entry-forms-web-${VERSION}
|
||||
aws s3 sync temp-deploy/hff-entry-forms-web-${VERSION} s3://forms.hopefamilyfellowship.com/${TARGET_ENV}/webroot
|
||||
tar xzf dist/hff-entry-forms-web-${VERSION}.tar.gz -C temp-deploy/hff-entry-forms-web-${VERSION}
|
||||
aws s3 sync temp-deploy/hff-entry-forms-web-${VERSION} s3://forms.hopefamilyfellowship.com/$(TARGET_ENV)/webroot
|
||||
TARGET_ENV=${TARGET_ENV} operations/invalidate-cdn-cache.sh
|
||||
rm -r temp-deploy
|
||||
|
||||
deploy: test deploy-api deploy-web
|
||||
deploy: deploy-api deploy-web
|
||||
|
79
README.md
79
README.md
@ -1,79 +0,0 @@
|
||||
# HFF Entry Forms
|
||||
|
||||
## Local Development
|
||||
|
||||
### Web
|
||||
|
||||
```sh
|
||||
cd web
|
||||
npm install
|
||||
make serve & # or run in a separate terminal/pane
|
||||
make build # as-needed to rebuild (not using hot-reload)
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
```sh
|
||||
cd api
|
||||
make serve
|
||||
|
||||
make serve-docker # alternatively
|
||||
```
|
||||
|
||||
## Procedures
|
||||
|
||||
### Version Bump
|
||||
|
||||
1. Ensure you know the current deployed version and the current "marked
|
||||
version." Check the current deployed version by hitting the live API:
|
||||
|
||||
curl https://forms-api.hopefamilyfellowship.com/v1/version
|
||||
|
||||
and by querying ECR:
|
||||
|
||||
aws ecr describe-images --repository-name 063932952339.dkr.ecr.us-west-2.amazonaws.com/hff_entry_forms_api
|
||||
|
||||
Check the current "marked version" by looking at the latest git tag:
|
||||
|
||||
git tag -n
|
||||
|
||||
2. If necessary, bump the version using:
|
||||
|
||||
make update-version
|
||||
|
||||
This will invoke `operations/update-version.sh` which:
|
||||
|
||||
- updates `web/package.json`,
|
||||
- updates `web/package-lock.json`,
|
||||
- updates `api/src/hff_entry_forms_apipkg/version.nim`,
|
||||
- updates `api/hff_entry_forms_api.nimble`,
|
||||
- commits all of the above, and
|
||||
- tags the commit with the new version.
|
||||
|
||||
#### Release Candidates (`-rcX` versions)
|
||||
|
||||
When we are preparing to release version a new version, we first create release
|
||||
candidates. So, in preparation for releasing, for example, version 1.2.4, we
|
||||
do the following:
|
||||
|
||||
```sh
|
||||
# Development is concluded, ready to release 1.2.4
|
||||
$ make update-version
|
||||
Last Version: 1.2.3
|
||||
New Version: 1.2.4-rc1
|
||||
|
||||
$ TARGET_ENV=dev make deploy
|
||||
# verify successful build and deployment
|
||||
# validate the release in the dev environment
|
||||
|
||||
$ make update-version
|
||||
Last Version: 1.2.4-rc1
|
||||
New Version: 1.2.4
|
||||
|
||||
$ TARGET_ENV=dev make deploy
|
||||
# verify successful build and deployment
|
||||
|
||||
$ TARGET_ENV=prod make deploy
|
||||
# verify successful build and deployment
|
||||
# validate the release in the prod environment
|
||||
```
|
@ -1,11 +1,12 @@
|
||||
FROM 063932952339.dkr.ecr.us-west-2.amazonaws.com/alpine-nim:nim-1.6.10 AS build
|
||||
FROM 063932952339.dkr.ecr.us-west-2.amazonaws.com/alpine-nim:nim-1.4.8 AS build
|
||||
MAINTAINER jonathan@jdbernard.com
|
||||
|
||||
COPY hff_entry_forms_api.nimble /hff_entry_forms_api/
|
||||
COPY src /hff_entry_forms_api/src
|
||||
WORKDIR /hff_entry_forms_api
|
||||
RUN nimble build -y --passC:-fpermissive
|
||||
RUN nimble build -y
|
||||
|
||||
FROM alpine:3.16.4
|
||||
FROM alpine
|
||||
EXPOSE 80
|
||||
RUN apk -v --update add --no-cache \
|
||||
ca-certificates \
|
||||
|
31
api/Makefile
31
api/Makefile
@ -4,16 +4,7 @@ SOURCES=$(wildcard src/*.nim) $(wildcard src/hff_entry_forms_apipkg/*.nim)
|
||||
ECR_ACCOUNT_URL ?= 063932952339.dkr.ecr.us-west-2.amazonaws.com
|
||||
|
||||
# The port on the host machine (not the container)
|
||||
ifeq ($(TARGET_ENV),prod)
|
||||
PORT=6006
|
||||
else
|
||||
PORT=6005
|
||||
endif
|
||||
|
||||
# The server to target when publishing the API
|
||||
TARGET_SERVER ?= sobeck.jdb-software.com
|
||||
|
||||
TARGET_ENV ?= local
|
||||
PORT ?= 8300
|
||||
|
||||
# The Notion integration token.
|
||||
AUTH_SECRET ?= 123abc
|
||||
@ -78,9 +69,6 @@ serve-docker: build-image
|
||||
# Utility
|
||||
# -------
|
||||
|
||||
clean:
|
||||
-docker image rm $(ECR_ACCOUNT_URL)/hff_entry_forms_api:$(VERSION)
|
||||
|
||||
# Authenticate docker to the AWS private elastic container repository.
|
||||
ecr-auth:
|
||||
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin $(ECR_ACCOUNT_URL)
|
||||
@ -91,20 +79,3 @@ echo-vars:
|
||||
"VERSION=$(VERSION)\n" \
|
||||
"PORT=$(PORT)\n" \
|
||||
"INTEGRATION_TOKEN=$(INTEGRATION_TOKEN)\n"
|
||||
|
||||
publish:
|
||||
-rm -r deploy
|
||||
-mkdir deploy
|
||||
m4 \
|
||||
-D "HFF_ENTRY_FORMS_API_VERSION=$(VERSION)" \
|
||||
-D "TARGET_ENV=$(TARGET_ENV)" \
|
||||
-D "TARGET_PORT=$(PORT)" \
|
||||
hff_entry_forms_api.service \
|
||||
> deploy/hff_entry_forms_api.$(TARGET_ENV).service
|
||||
-ssh deployer@$(TARGET_SERVER) "docker stop hff_entry_forms_api.$(TARGET_ENV).service && sudo systemctl stop hff_entry_forms_api.$(TARGET_ENV)"
|
||||
ssh deployer@$(TARGET_SERVER) "aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin $(ECR_ACCOUNT_URL) && docker pull $(ECR_ACCOUNT_URL)/hff_entry_forms_api:$(VERSION)"
|
||||
scp \
|
||||
deploy/hff_entry_forms_api.$(TARGET_ENV).service \
|
||||
deployer@$(TARGET_SERVER):/etc/systemd/system/hff_entry_forms_api.$(TARGET_ENV).service
|
||||
ssh deployer@$(TARGET_SERVER) "sudo systemctl daemon-reload"
|
||||
ssh deployer@$(TARGET_SERVER) "sudo systemctl start hff_entry_forms_api.$(TARGET_ENV)"
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Package
|
||||
|
||||
version = "0.3.3"
|
||||
version = "0.2.2"
|
||||
author = "Jonathan Bernard"
|
||||
description = "Hope Family Fellowship entry forms."
|
||||
license = "GPL-3.0-or-later"
|
||||
@ -15,11 +15,7 @@ requires @["docopt", "jester"]
|
||||
|
||||
requires "https://git.jdb-software.com/jdb/nim-cli-utils.git >= 0.6.3"
|
||||
requires "https://git.jdb-software.com/jdb/nim-time-utils.git >= 0.5.0"
|
||||
requires "https://git.jdb-software.com/jdb/update-nim-package-version.git"
|
||||
requires "https://git.jdb-software.com/hope-family-fellowship/notion-api-client.git"
|
||||
requires "https://git.jdb-software.com/jdb/update-nim-package-version"
|
||||
|
||||
task updateVersion, "Update the version of this package.":
|
||||
exec "update_nim_package_version hff_entry_forms_api 'src/hff_entry_forms_apipkg/version.nim'"
|
||||
|
||||
task unittest, "Runs the unit test suite.":
|
||||
exec "nim c -r test/runner"
|
||||
exec "update_nim_package_version hff_entry_forms_api 'src/hff_entry_forms_apipkg/version.nim'"
|
@ -1,16 +0,0 @@
|
||||
[Unit]
|
||||
Description=HFF Entry Forms (TARGET_ENV)
|
||||
After=network-online.target
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
TimeoutStartSec=0
|
||||
Restart=always
|
||||
ExecStartPre=-/usr/bin/docker rm %n
|
||||
ExecStart=/usr/bin/docker run --rm -p TARGET_PORT:80 --name %n \
|
||||
--env-file /etc/hff_entry_forms/TARGET_ENV.env \
|
||||
063932952339.dkr.ecr.us-west-2.amazonaws.com/hff_entry_forms_api:HFF_ENTRY_FORMS_API_VERSION
|
||||
ExecStop=/usr/bin/docker stop --name %n
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
@ -1,5 +1,4 @@
|
||||
import std/[json, logging, os, sequtils, strutils, tables]
|
||||
import cliutils, docopt
|
||||
import cliutils, docopt, json, logging, sequtils, strutils, tables
|
||||
|
||||
import hff_entry_forms_apipkg/api
|
||||
import hff_entry_forms_apipkg/version
|
||||
@ -27,13 +26,12 @@ proc loadConfig(args: Table[string, docopt.Value]): HffEntryFormsApiConfig =
|
||||
let cfg = CombinedConfig(docopt: args, json: json)
|
||||
|
||||
result = HffEntryFormsApiConfig(
|
||||
debug: cfg.hasKey("debug") and cfg.getVal("debug") == "true",
|
||||
debug: args["--debug"],
|
||||
eventParentId: cfg.getVal("event-parent-id"),
|
||||
integrationToken: cfg.getVal("integration-token"),
|
||||
knownOrigins: cfg.getVal("known-origins")[1..^2].split(',').mapIt(it.strip[1..^2]),
|
||||
knownOrigins: cfg.getVal("known-origins")[1..^2].split(',').mapIt(it[1..^2]),
|
||||
notionApiBaseUrl: cfg.getVal("notion-api-base-url"),
|
||||
notionVersion: cfg.getVal("notion-version"),
|
||||
notionConfigDbId: cfg.getVal("notion-config-db-id"),
|
||||
port: parseInt(cfg.getVal("port", "8300")))
|
||||
|
||||
when isMainModule:
|
||||
@ -44,7 +42,7 @@ Usage:
|
||||
|
||||
Options:
|
||||
|
||||
-c, --config <cfgFile> Location of the config file to use (defaults to
|
||||
-C, --config <cfgFile> Location of the config file to use (defaults to
|
||||
hff_entry_forms_api.config.json)
|
||||
|
||||
-d, --debug Log debugging information.
|
||||
@ -54,18 +52,17 @@ Options:
|
||||
|
||||
let consoleLogger = newConsoleLogger(
|
||||
levelThreshold=lvlInfo,
|
||||
fmtStr="$appname - $levelname: ",
|
||||
useStderr=true)
|
||||
fmtStr="$app - $levelname: ")
|
||||
logging.addHandler(consoleLogger)
|
||||
|
||||
# Initialize our service context
|
||||
let args = docopt(doc, version = HFF_ENTRY_FORMS_API_VERSION)
|
||||
|
||||
let cfg = loadConfig(args)
|
||||
|
||||
if cfg.debug:
|
||||
if args["--debug"]:
|
||||
consoleLogger.levelThreshold = lvlDebug
|
||||
|
||||
let cfg = loadConfig(args)
|
||||
|
||||
if args["serve"]: start(cfg)
|
||||
|
||||
except:
|
||||
|
@ -26,7 +26,7 @@ template jsonResp(code: HttpCode,
|
||||
headersToSend: RawHeaders = @{:} ) =
|
||||
## Immediately send a JSON response and stop processing the request.
|
||||
let reqOrigin =
|
||||
if headers(request).hasKey("Origin"): $(headers(request)["Origin"])
|
||||
if request.headers.hasKey("Origin"): $(request.headers["Origin"])
|
||||
else: ""
|
||||
|
||||
let corsHeaders =
|
||||
@ -34,13 +34,11 @@ template jsonResp(code: HttpCode,
|
||||
@{
|
||||
"Access-Control-Allow-Origin": reqOrigin,
|
||||
"Access-Control-Allow-Credentials": "true",
|
||||
"Access-Control-Allow-Methods": $(reqMethod(request)),
|
||||
"Access-Control-Allow-Methods": $(request.reqMethod),
|
||||
"Access-Control-Allow-Headers": "Authorization,X-CSRF-TOKEN"
|
||||
}
|
||||
else: @{:}
|
||||
|
||||
#debug "Request origin: $#\nKnown origins: $#\nAdding headers:\n$#" %
|
||||
# [ reqOrigin, cfg.knownOrigins.join(" | "), $corsHeaders ]
|
||||
halt(
|
||||
code,
|
||||
headersToSend & corsHeaders & @{
|
||||
@ -83,7 +81,7 @@ template errorResp(err: ref ApiError): void =
|
||||
template optionsResp(allowedMethods: seq[HttpMethod]) =
|
||||
|
||||
let reqOrigin =
|
||||
if headers(request).hasKey("Origin"): $(headers(request)["Origin"])
|
||||
if request.headers.hasKey("Origin"): $(request.headers["Origin"])
|
||||
else: ""
|
||||
|
||||
let corsHeaders =
|
||||
|
@ -1,6 +1,4 @@
|
||||
import std/json, std/times
|
||||
import hff_notion_api_client/utils, timeutils
|
||||
|
||||
import json, times, timeutils
|
||||
|
||||
type
|
||||
EventProposal* = object
|
||||
@ -28,6 +26,14 @@ proc getOrFail(n: JsonNode, key: string): JsonNode =
|
||||
proc parseIso8601(n: JsonNode, key: string): DateTime =
|
||||
return parseIso8601(n.getOrFail(key).getStr)
|
||||
|
||||
proc textProp(value: string): JsonNode =
|
||||
return %*[
|
||||
{
|
||||
"type": "text",
|
||||
"text": { "content": value }
|
||||
}
|
||||
]
|
||||
|
||||
proc parseEventProposal*(n: JsonNode): EventProposal {.raises: [JsonParsingError].} =
|
||||
|
||||
try:
|
||||
@ -43,37 +49,36 @@ proc parseEventProposal*(n: JsonNode): EventProposal {.raises: [JsonParsingError
|
||||
except:
|
||||
raise newException(JsonParsingError, "Invalid EventProposal: " & getCurrentExceptionMsg())
|
||||
|
||||
proc toPage*(ep: EventProposal): JsonNode =
|
||||
proc asNotionPage*(ep: EventProposal): JsonNode =
|
||||
result = %*{
|
||||
"properties": {
|
||||
"Event": makeTextProp("title", ep.name),
|
||||
"Event": { "title": textProp(ep.name) },
|
||||
"Date": { "date": { "start": formatIso8601(ep.date) } },
|
||||
"Department": { "multi_select": [ { "name": ep.department } ] },
|
||||
"Location": makeTextProp("rich_text", ep.location),
|
||||
"Owner": makeTextProp("rich_text", ep.owner),
|
||||
"State": { "select": { "name": "Proposed" } },
|
||||
"Visibility": { "select": { "name": "Public" } }
|
||||
"Location": { "rich_text": textProp(ep.location) },
|
||||
"Owner": { "rich_text": textProp(ep.owner) },
|
||||
"State": { "select": { "name": "Proposed" } }
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"object": "block",
|
||||
"type": "heading_2",
|
||||
"heading_2": makeTextProp("text", "Purpose")
|
||||
"heading_2": { "text": textProp("Purpose") }
|
||||
},
|
||||
{
|
||||
"object": "block",
|
||||
"type": "paragraph",
|
||||
"paragraph": makeTextProp("text", ep.purpose)
|
||||
"paragraph": { "text": textProp(ep.purpose) }
|
||||
},
|
||||
{
|
||||
"object": "block",
|
||||
"type": "heading_2",
|
||||
"heading_2": makeTextProp("text", "Description")
|
||||
"heading_2": { "text": textProp("Description") }
|
||||
},
|
||||
{
|
||||
"object": "block",
|
||||
"type": "paragraph",
|
||||
"paragraph": makeTextProp("text", ep.description)
|
||||
"paragraph": { "text": textProp(ep.description) }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,29 +1,28 @@
|
||||
import std/[json, httpclient, logging, options, sequtils, strutils]
|
||||
import hff_notion_api_client
|
||||
import hff_notion_api_client/config
|
||||
import json, logging, std/httpclient, sequtils, strutils
|
||||
|
||||
import ./models, ./service
|
||||
|
||||
var notionClient = none[NotionClient]()
|
||||
|
||||
proc getNotionClient(cfg: HffEntryFormsApiConfig): NotionClient =
|
||||
if notionClient.isNone:
|
||||
notionClient = some(initNotionClient(NotionClientConfig(
|
||||
apiVersion: cfg.notionVersion,
|
||||
apiBaseUrl: cfg.notionApiBaseUrl,
|
||||
configDbId: cfg.notionConfigDbId,
|
||||
integrationToken: cfg.integrationToken)))
|
||||
return notionClient.get
|
||||
proc makeHttpClient(cfg: HffEntryFormsApiConfig): HttpClient =
|
||||
let headers = newHttpHeaders([
|
||||
("Content-Type", "application/json"),
|
||||
("Authorization", "Bearer " & cfg.integrationToken),
|
||||
("Notion-Version", cfg.notionVersion)
|
||||
], true)
|
||||
debug $headers
|
||||
return newHttpClient(headers = headers, )
|
||||
|
||||
proc getEventProposalConfig*(cfg: HffEntryFormsApiConfig): EventProposalConfig =
|
||||
let notion = getNotionClient(cfg)
|
||||
let http = makeHttpClient(cfg)
|
||||
|
||||
var bodyJson: JsonNode
|
||||
try: bodyJson = notion.fetchDatabaseObject(cfg.eventParentId)
|
||||
except:
|
||||
let apiResp = http.get(cfg.notionApiBaseUrl & "/databases/" & cfg.eventParentId)
|
||||
debug apiResp.status
|
||||
|
||||
if not apiResp.status.startsWith("2"):
|
||||
debug apiResp.body
|
||||
raiseApiError(Http500,
|
||||
"unable to read event propsal configuration from notion API")
|
||||
|
||||
let bodyJson = parseJson(apiResp.body)
|
||||
let departmentOptionsJson = bodyJson{
|
||||
"properties", "Department", "multi_select", "options"}
|
||||
|
||||
@ -38,9 +37,13 @@ proc getEventProposalConfig*(cfg: HffEntryFormsApiConfig): EventProposalConfig =
|
||||
)
|
||||
|
||||
proc createProposedEvent*(cfg: HffEntryFormsApiConfig, ep: EventProposal): bool =
|
||||
let notion = getNotionClient(cfg)
|
||||
let http = makeHttpClient(cfg)
|
||||
let epNotionPage = ep.asNotionPage
|
||||
epNotionPage["parent"] = %*{ "database_id": cfg.eventParentId }
|
||||
|
||||
try:
|
||||
discard notion.createDbPage(cfg.eventParentId, ep)
|
||||
return true
|
||||
except: return false
|
||||
let apiResp = http.post(cfg.notionApiBaseUrl & "/pages", $epNotionPage)
|
||||
|
||||
debug apiResp.status
|
||||
if not apiResp.status.startsWith("2"): debug apiResp.body
|
||||
|
||||
return apiResp.status.startsWith("2")
|
||||
|
@ -12,7 +12,6 @@ type
|
||||
knownOrigins*: seq[string]
|
||||
notionApiBaseUrl*: string
|
||||
notionVersion*: string
|
||||
notionConfigDbId*: string
|
||||
port*: int
|
||||
|
||||
proc newApiError*(parent: ref Exception = nil, respCode: HttpCode, respMsg: string, msg = ""): ref ApiError =
|
||||
|
@ -1 +1 @@
|
||||
const HFF_ENTRY_FORMS_API_VERSION* = "0.3.3"
|
||||
const HFF_ENTRY_FORMS_API_VERSION* = "0.2.2"
|
@ -1,2 +0,0 @@
|
||||
switch("path", "../src")
|
||||
switch("verbosity", "0")
|
@ -1,3 +0,0 @@
|
||||
import std/unittest
|
||||
|
||||
import ./tmodels
|
@ -1,20 +0,0 @@
|
||||
import std/json, std/times, std/unittest
|
||||
|
||||
import hff_entry_forms_apipkg/models
|
||||
|
||||
suite "models":
|
||||
|
||||
test "toPage(EventProposal)":
|
||||
let ep = EventProposal(
|
||||
name: "Test Event",
|
||||
description: "A test event.",
|
||||
purpose: "Event example for unit testing.",
|
||||
department: "Testing",
|
||||
location: "Hope Family Fellowship",
|
||||
owner: "Jonathan Bernard",
|
||||
date: parse("2021-10-30", "YYYY-MM-dd"),
|
||||
budgetInDollars: 56)
|
||||
|
||||
let expectedJson = """{"properties":{"Event":{"title":[{"type":"text","text":{"content":"Test Event"}}]},"Date":{"date":{"start":"2021-10-30T00:00:00-05:00"}},"Department":{"multi_select":[{"name":"Testing"}]},"Location":{"rich_text":[{"type":"text","text":{"content":"Hope Family Fellowship"}}]},"Owner":{"rich_text":[{"type":"text","text":{"content":"Jonathan Bernard"}}]},"State":{"select":{"name":"Proposed"}},"Visibility":{"select":{"name":"Public"}}},"children":[{"object":"block","type":"heading_2","heading_2":{"text":[{"type":"text","text":{"content":"Purpose"}}]}},{"object":"block","type":"paragraph","paragraph":{"text":[{"type":"text","text":{"content":"Event example for unit testing."}}]}},{"object":"block","type":"heading_2","heading_2":{"text":[{"type":"text","text":{"content":"Description"}}]}},{"object":"block","type":"paragraph","paragraph":{"text":[{"type":"text","text":{"content":"A test event."}}]}}]}"""
|
||||
|
||||
check $(ep.toPage) == expectedJson
|
@ -2,12 +2,10 @@
|
||||
|
||||
## System Components
|
||||
|
||||
* DNS - managed by AWS
|
||||
* API Server - hosted on `sobeck.jdb-software.com`
|
||||
* DNS - hosted on GoDaddy
|
||||
* API Server - hosted on the `ortis` ECS cluster at JDB Software
|
||||
* API Loadbalancer - using the main load balancer for JDB Software
|
||||
* Web App - Served by CloudFront from an S3 bucket managed by JDB Software
|
||||
* Certificates - Manually created and validated for \*.HFF.com
|
||||
* Notion Integration - defined in Notion, token provided via the environment
|
||||
specific config files at `/etc/hff_entry_forms/{dev,prod}.env` and passed
|
||||
into the docker container at runtime ad defined in the SystemD service file
|
||||
(see `api/hff_entry_forms_api.service`)
|
||||
* Notion Integration - defined in Notion, token provided via AWS secrets
|
||||
manager to the API instance running on the ortis cluster.
|
||||
|
36
operations/opentofu/.terraform.lock.hcl
generated
36
operations/opentofu/.terraform.lock.hcl
generated
@ -1,36 +0,0 @@
|
||||
# This file is maintained automatically by "tofu init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/aws" {
|
||||
version = "5.62.0"
|
||||
hashes = [
|
||||
"h1:DzXMlmL2hRPfACAbN1PUhnLDGY9Kl0vbrt05qSfGsxA=",
|
||||
"zh:2cb519ce7f3cbcb88b2e93dd3b3424ad85a347fc0e7429661945da5df8a20fda",
|
||||
"zh:2fc7ed911cceaa1652d1f4090eaa91e8463aba86873910bccf16601260379886",
|
||||
"zh:395b32d157adeb92571a0efd230c73bbee01744782a50356fb16e8946bd63ffb",
|
||||
"zh:43303d36af40a568cd40bd54dc9e8430e18c4a4d78682b459dca8c755c717a0c",
|
||||
"zh:65b2c6e955deeeffb9d9cd4ed97e8c532a453ba690d0e3d88c740f9036bccc4d",
|
||||
"zh:a9d09dc9daf33b16894ed7d192ceb4c402261da58cded503a3ffa1dd2373e3fb",
|
||||
"zh:c5e9f8bc4397c2075b6dc62458be51b93322517affd760c161633d56b0b9a334",
|
||||
"zh:db0921c091402179edd549f8aa4f12dce18aab09d4302e800c67d6ec6ff88a86",
|
||||
"zh:e7d13f9c0891446d03c29e4fcd60de633f71bbf1bc9786fca47a0ee356ac979a",
|
||||
"zh:f128a725dbdbd31b9ed8ea478782152339c9fab4d635485763c8da2a477fe3f6",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.opentofu.org/hashicorp/external" {
|
||||
version = "2.3.3"
|
||||
hashes = [
|
||||
"h1:bDJy8Mj5PMTEuxm6Wu9A9dATBL+mQDmHx8NnLzjvCcc=",
|
||||
"zh:1ec36864a1872abdfd1c53ba3c6837407564ac0d86ab80bf4fdc87b41106fe68",
|
||||
"zh:2117e0edbdc88f0d22fe02fe6b2cfbbbc5d5ce40f8f58e484d8d77d64dd7340f",
|
||||
"zh:4bcfdacd8e2508c16e131de9072cecd359e0ade3b8c6798a049883f37a5872ea",
|
||||
"zh:4da71bc601a37bf8b7413c142d43f5f28e97e531d4836ee8624f41b9fb62e250",
|
||||
"zh:55b9eebac79a46f88db5615f1ee0ac4c3f9351caa4eb8542171ef5d87de60338",
|
||||
"zh:74d64afaef190321f8ddf1c4a9c6489d6cf51098704a2456c1553406e8306328",
|
||||
"zh:8a357e51a0ec69872fafc64da3c6a1039277d325255ef5a264b727d83995d18b",
|
||||
"zh:aacd2e6c13fe19115d51cd28a40a28da017bb48c2e18dec4460d1c37506b1495",
|
||||
"zh:e19c8bdf0e059341d008a50f9138c44009e9ebb3a8047a300e6bc63ed8af8ea0",
|
||||
"zh:fafa9639d8b8402e35f3864c6cfb0762ec57cc365a8f383e2acf81105b1b9eea",
|
||||
]
|
||||
}
|
70
operations/terraform/deployed_env/ecs.tf
Normal file
70
operations/terraform/deployed_env/ecs.tf
Normal file
@ -0,0 +1,70 @@
|
||||
resource "aws_secretsmanager_secret" "hff_entry_forms_api" {
|
||||
name = "${local.environment_name}-Config"
|
||||
tags = { Environment = local.environment_name }
|
||||
}
|
||||
|
||||
resource "aws_ecs_task_definition" "hff_entry_forms_api" {
|
||||
family = local.environment_name
|
||||
network_mode = "bridge"
|
||||
requires_compatibilities = ["EC2"]
|
||||
execution_role_arn = aws_iam_role.ecs_task.arn
|
||||
|
||||
# See https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html
|
||||
container_definitions = jsonencode([
|
||||
{
|
||||
name = local.environment_name
|
||||
image = "${var.ecr_repo.repository_url}:${data.external.git_describe.result.version}"
|
||||
cpu = 128
|
||||
memory = 128
|
||||
memoryReservation = 32
|
||||
environment = [
|
||||
{
|
||||
name = "PORT"
|
||||
value = "80"
|
||||
}
|
||||
]
|
||||
portMappings = [
|
||||
{
|
||||
protocol = "tcp"
|
||||
containerPort = 80
|
||||
}
|
||||
]
|
||||
secrets = [
|
||||
{
|
||||
name = "INTEGRATION_TOKEN"
|
||||
description = "Connection string with user credentials."
|
||||
valueFrom = "${aws_secretsmanager_secret.hff_entry_forms_api.arn}:integrationToken::"
|
||||
},
|
||||
{
|
||||
name = "KNOWN_ORIGINS"
|
||||
description = "Connection string with user credentials."
|
||||
valueFrom = "${aws_secretsmanager_secret.hff_entry_forms_api.arn}:knownOrigins::"
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
tags = {
|
||||
Name = local.api_domain_name
|
||||
Environment = local.environment_name
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ecs_service" "hff_entry_forms_api" {
|
||||
name = local.environment_name
|
||||
cluster = data.terraform_remote_state.jdbsoft.outputs.aws_ecs_cluster_ortis.id
|
||||
task_definition = aws_ecs_task_definition.hff_entry_forms_api.arn
|
||||
desired_count = 1
|
||||
launch_type = "EC2"
|
||||
|
||||
load_balancer {
|
||||
target_group_arn = aws_lb_target_group.hff_entry_forms_api.arn
|
||||
container_name = local.environment_name
|
||||
container_port = 80
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = local.api_domain_name
|
||||
Environment = local.environment_name
|
||||
}
|
||||
}
|
69
operations/terraform/deployed_env/iam.tf
Normal file
69
operations/terraform/deployed_env/iam.tf
Normal file
@ -0,0 +1,69 @@
|
||||
resource "aws_iam_role" "ecs_task" {
|
||||
name = "${local.environment_name}-EcsTaskRole"
|
||||
|
||||
assume_role_policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Action = "sts:AssumeRole"
|
||||
Effect = "Allow"
|
||||
Sid = ""
|
||||
Principal = {
|
||||
Service = "ecs-tasks.amazonaws.com"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
inline_policy {
|
||||
name = "AllowSecretsAccessForHffEntryFormsApiTasks"
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"secretsmanager:GetSecretValue",
|
||||
"kms:Decrypt"
|
||||
]
|
||||
Resource = [
|
||||
aws_secretsmanager_secret.hff_entry_forms_api.arn
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
inline_policy {
|
||||
name = "AllowAccessToEcrForHffEntryFormsApiTasks"
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:GetAuthorizationToken"
|
||||
]
|
||||
Resource = [ "*" ]
|
||||
},
|
||||
{
|
||||
Effect = "Allow"
|
||||
Action = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:DescribeImages",
|
||||
"ecr:GetDownloadUrlForLayer"
|
||||
]
|
||||
Resource = [
|
||||
var.ecr_repo.arn
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "HffEntryForms-EcsTaskRole"
|
||||
Environment = local.environment_name
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
resource "aws_lb_target_group" "hff_entry_forms_api" {
|
||||
name = "${local.environment_name}-${substr(uuid(), 0, 2)}"
|
||||
port = var.target_port
|
||||
port = 80
|
||||
protocol = "HTTP"
|
||||
target_type = "instance"
|
||||
vpc_id = data.terraform_remote_state.jdbsoft.outputs.aws_vpc_jdbsoft.id
|
||||
@ -41,9 +41,3 @@ resource "aws_lb_listener_rule" "hff_entry_forms_api" {
|
||||
Environment = local.environment_name
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_lb_target_group_attachment" "hff_entry_forms_api" {
|
||||
target_group_arn = aws_lb_target_group.hff_entry_forms_api.arn
|
||||
target_id = data.terraform_remote_state.jdbsoft.outputs.sobeck-instance-id
|
||||
port = var.target_port
|
||||
}
|
@ -12,10 +12,6 @@ variable "ecr_repo" {
|
||||
description = "ECR repository information."
|
||||
}
|
||||
|
||||
variable "target_port" {
|
||||
description = "The port the deployed service will listen on."
|
||||
}
|
||||
|
||||
variable "api_certificate_arn" {
|
||||
description = "ARN of the certificate to use for the API loadbalancer."
|
||||
}
|
@ -15,7 +15,6 @@ module "dev_env" {
|
||||
artifact_bucket = aws_s3_bucket.hff_entry_forms
|
||||
cloudfront_certificate_arn = var.cloudfront_certificate_arn
|
||||
ecr_repo = aws_ecr_repository.hff_entry_forms_api
|
||||
target_port = 6005
|
||||
}
|
||||
|
||||
module "prod_env" {
|
||||
@ -26,14 +25,11 @@ module "prod_env" {
|
||||
artifact_bucket = aws_s3_bucket.hff_entry_forms
|
||||
cloudfront_certificate_arn = var.cloudfront_certificate_arn
|
||||
ecr_repo = aws_ecr_repository.hff_entry_forms_api
|
||||
target_port = 6006
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "cloudfront_access_policy" {
|
||||
source_policy_documents = [
|
||||
module.dev_env.oai_access_policy.json,
|
||||
module.prod_env.oai_access_policy.json
|
||||
]
|
||||
source_json = "${module.dev_env.oai_access_policy.json}"
|
||||
override_json = "${module.prod_env.oai_access_policy.json}"
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_policy" "hff_entry_forms" {
|
@ -10,7 +10,7 @@ rootDir=$(git rev-parse --show-toplevel)
|
||||
cd "$rootDir"
|
||||
|
||||
currentBranch=$(git rev-parse --abbrev-ref HEAD)
|
||||
if [ "$currentBranch" != "main" ]; then
|
||||
if [ "$currentBranch" != "develop" ]; then
|
||||
printf "You are currently on the '%s' branch. Is this intended (yes/no)? " "$currentBranch"
|
||||
read -r confirmation
|
||||
|
||||
@ -60,3 +60,4 @@ git commit -m "Update package version to ${newVersion}"
|
||||
printf ">> Tagging commit.\n"
|
||||
printf "git tag -m \"Version %s\" \"%s\"\n" "$newVersion" "$newVersion"
|
||||
git tag -m "Version ${newVersion}" "${newVersion}"
|
||||
|
||||
|
@ -3,6 +3,3 @@ build:
|
||||
|
||||
serve:
|
||||
npm run serve
|
||||
|
||||
clean:
|
||||
-rm -r dist
|
||||
|
24
web/README.md
Normal file
24
web/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# hff-entry-form-web
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
13563
web/package-lock.json
generated
13563
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hff-entry-form-web",
|
||||
"version": "0.3.3",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "npx servor dist",
|
||||
@ -30,7 +30,6 @@
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"git-describe": "^4.1.0",
|
||||
"prettier": "^2.2.1",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
|
@ -2,14 +2,9 @@
|
||||
|
||||
html { font-size: 16px; }
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: "Segoe UI", Helvetica, Arial, sans-serif;
|
||||
box-sizing: border-box;
|
||||
|
||||
button, input, select, textarea {
|
||||
font-size: inherit;
|
||||
@ -19,7 +14,6 @@ html { font-size: 16px; }
|
||||
|
||||
button {
|
||||
color: #1084AC;
|
||||
cursor: pointer;
|
||||
background-color: #1084AC0A;
|
||||
border: solid thin #1084AC;
|
||||
border-radius: 0.25em;
|
||||
@ -43,26 +37,6 @@ html { font-size: 16px; }
|
||||
}
|
||||
|
||||
&:disabled { opacity: 0.75; }
|
||||
|
||||
}
|
||||
|
||||
button.low-profile {
|
||||
color: black;
|
||||
background-color: unset;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
font-weight: normal;
|
||||
transition-property: color, font-weight;
|
||||
transition-duration: 250ms;
|
||||
|
||||
&:not(:disabled) {
|
||||
&:hover, &:focus {
|
||||
color: #1084AC;
|
||||
font-weight: bold;
|
||||
background-color: unset;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,20 +66,8 @@ html { font-size: 16px; }
|
||||
|
||||
@include forSize(mobile) {
|
||||
body { margin: 0; }
|
||||
|
||||
#app #debug-information .mobile { display: inline-block; }
|
||||
}
|
||||
|
||||
@include forSize(tablet) {
|
||||
#app #debug-information .tablet { display: inline-block; }
|
||||
}
|
||||
|
||||
@include forSize(desktop) {
|
||||
#app #debug-information .desktop { display: inline-block; }
|
||||
}
|
||||
|
||||
@include forSize(ultrawide) {
|
||||
html { font-size: 24px; }
|
||||
|
||||
#app #debug-information .ultrawide { display: inline-block; }
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import DebugInfo from '@/components/DebugInfo.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { DebugInfo },
|
||||
});
|
@ -1,6 +1,4 @@
|
||||
<template>
|
||||
<router-view />
|
||||
<DebugInfo />
|
||||
</template>
|
||||
<script lang="ts" src="./App.ts"></script>
|
||||
<style lang="scss" src="./App.scss"></style>
|
||||
|
@ -1,23 +0,0 @@
|
||||
#debug-information {
|
||||
position: fixed;
|
||||
left: 1em;
|
||||
bottom: 1em;
|
||||
opacity: 0.5;
|
||||
font-size: 80%;
|
||||
|
||||
.toggle-content { display: none; }
|
||||
.toggle-content.visible { display: block; }
|
||||
|
||||
.mobile, .tablet, .desktop, .ultrawide { display: none; }
|
||||
|
||||
ul, li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 8em;
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
import EyeClosedIcon from '@/components/svg/EyeClosedIcon.vue';
|
||||
import EyeOpenIcon from '@/components/svg/EyeOpenIcon.vue';
|
||||
import VERSION from '@/version-info';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DebugInfo',
|
||||
components: { EyeOpenIcon, EyeClosedIcon },
|
||||
setup: function DebugInfo() {
|
||||
const showDebugInfo = ref(false);
|
||||
const version = ref(VERSION);
|
||||
|
||||
return { showDebugInfo, version };
|
||||
},
|
||||
});
|
@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<div id="debug-information">
|
||||
<div class="toggle-content" :class="{ visible: showDebugInfo }">
|
||||
<ul>
|
||||
<li class="version"><label>version:</label>{{ version.version }}</li>
|
||||
<li class="hash"><label>build hash:</label>{{ version.hash }}</li>
|
||||
<li class="raw"><label>build hash (raw):</label>{{ version.raw }}</li>
|
||||
</ul>
|
||||
<label>device size: </label>
|
||||
<label class="mobile">mobile</label>
|
||||
<label class="tablet">tablet</label>
|
||||
<label class="desktop">desktop</label>
|
||||
<label class="ultrawide">ultrawide</label>
|
||||
</div>
|
||||
<button class="low-profile" @click="showDebugInfo = !showDebugInfo">
|
||||
<EyeOpenIcon v-if="showDebugInfo" />
|
||||
<EyeClosedIcon v-if="!showDebugInfo" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" src="./DebugInfo.ts"></script>
|
||||
<style scoped lang="scss" src="./DebugInfo.scss"></style>
|
@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14.7649 6.07595C14.9991 6.22231 15.0703 6.53078 14.9239 6.76495C14.4849 7.46742 13.9632 8.10644 13.3702 8.66304L14.5712 9.86405C14.7664 10.0593 14.7664 10.3759 14.5712 10.5712C14.3759 10.7664 14.0593 10.7664 13.8641 10.5712L12.6011 9.30816C11.8049 9.90282 10.9089 10.3621 9.93374 10.651L10.383 12.3276C10.4544 12.5944 10.2961 12.8685 10.0294 12.94C9.76266 13.0115 9.4885 12.8532 9.41703 12.5864L8.95916 10.8775C8.48742 10.958 8.00035 10.9999 7.5 10.9999C6.99964 10.9999 6.51257 10.958 6.04082 10.8775L5.58299 12.5864C5.51153 12.8532 5.23737 13.0115 4.97063 12.94C4.7039 12.8685 4.5456 12.5944 4.61706 12.3277L5.06624 10.651C4.09111 10.3621 3.19503 9.90281 2.3989 9.30814L1.1359 10.5711C0.940638 10.7664 0.624058 10.7664 0.428797 10.5711C0.233537 10.3759 0.233537 10.0593 0.428797 9.86404L1.62982 8.66302C1.03682 8.10643 0.515113 7.46742 0.0760677 6.76495C-0.0702867 6.53078 0.000898544 6.22231 0.235064 6.07595C0.46923 5.9296 0.777703 6.00078 0.924057 6.23495C1.40354 7.00212 1.989 7.68056 2.66233 8.2427C2.67315 8.25096 2.6837 8.25971 2.69397 8.26897C4.00897 9.35527 5.65536 9.9999 7.5 9.9999C10.3078 9.9999 12.6563 8.50629 14.0759 6.23495C14.2223 6.00078 14.5308 5.9296 14.7649 6.07595Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M7.5 11C4.80285 11 2.52952 9.62184 1.09622 7.50001C2.52952 5.37816 4.80285 4 7.5 4C10.1971 4 12.4705 5.37816 13.9038 7.50001C12.4705 9.62183 10.1971 11 7.5 11ZM7.5 3C4.30786 3 1.65639 4.70638 0.0760002 7.23501C-0.0253338 7.39715 -0.0253334 7.60288 0.0760014 7.76501C1.65639 10.2936 4.30786 12 7.5 12C10.6921 12 13.3436 10.2936 14.924 7.76501C15.0253 7.60288 15.0253 7.39715 14.924 7.23501C13.3436 4.70638 10.6921 3 7.5 3ZM7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5C9.5 6.39543 8.60457 5.5 7.5 5.5C6.39543 5.5 5.5 6.39543 5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,17 +0,0 @@
|
||||
export interface VersionInfo {
|
||||
version: string;
|
||||
hash: string;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
const gitVersion: { hash: string; raw: string } = process.env
|
||||
.VUE_APP_HFF_ENTRY_FORMS_GIT_HASH
|
||||
? JSON.parse(process.env.VUE_APP_HFF_ENTRY_FORMS_GIT_HASH)
|
||||
: { hash: 'missing', raw: 'missing' };
|
||||
|
||||
export const VERSION: VersionInfo = {
|
||||
...gitVersion,
|
||||
version: process.env.VUE_APP_HFF_ENTRY_FORMS_VERSION || 'unavailable',
|
||||
};
|
||||
|
||||
export default VERSION;
|
@ -1,22 +1,20 @@
|
||||
@import "~@/styles/forSize.mixin";
|
||||
|
||||
#event-proposal {
|
||||
margin: 2em auto;
|
||||
|
||||
header {
|
||||
background-color: #FFFCFC;
|
||||
border-bottom: thin solid #aaa;
|
||||
margin: 0;
|
||||
padding: 0.5em;
|
||||
|
||||
h1, h2 {
|
||||
margin: 0;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
margin-bottom: -0.30em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.success header { background-color: #38b00010; }
|
||||
@ -42,6 +40,7 @@
|
||||
margin: 0.5em;
|
||||
display: flex;
|
||||
|
||||
span { width: 9em; }
|
||||
input, textarea, select { flex-grow: 1; }
|
||||
}
|
||||
|
||||
@ -86,21 +85,12 @@
|
||||
@include forSize(mobile) {
|
||||
#header-splash {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#event-proposal {
|
||||
padding: 0 1em;
|
||||
margin: 1em;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
flex-wrap: wrap;
|
||||
|
||||
span { width: 100%; }
|
||||
input, select, textarea {
|
||||
margin: 0.125em 1em;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,10 +106,7 @@
|
||||
border: solid thin #bbb;
|
||||
border-radius: 0.25em;
|
||||
box-shadow: 0.25em 0.25em 0.75em #aaa;
|
||||
margin: 2em auto;
|
||||
width: 30em;
|
||||
|
||||
label span { width: 9em; }
|
||||
}
|
||||
|
||||
.successes, .errors { width: 26em; }
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { defineComponent, Ref, ref } from 'vue';
|
||||
import { logService } from '@jdbernard/logging';
|
||||
import {
|
||||
default as api,
|
||||
@ -28,8 +28,8 @@ export default defineComponent({
|
||||
props: {},
|
||||
components: { CircleCheckIcon, CircleCrossIcon, HourGlassIcon, SpinnerIcon },
|
||||
setup: function TheProposeEventView() {
|
||||
const departments = ref<{ value: string; color: string }[]>([]);
|
||||
const formState = ref<FormState>('loading');
|
||||
const departments: Ref<{ value: string; color: string }[]> = ref([]);
|
||||
const formState: Ref<FormState> = ref('loading');
|
||||
|
||||
setTimeout(async () => {
|
||||
departments.value = (await api.getEventProposalConfig()).departments;
|
||||
@ -62,14 +62,14 @@ export default defineComponent({
|
||||
if (await api.proposeEvent(formVal.event)) {
|
||||
formState.value = 'success';
|
||||
successes.push(
|
||||
`We've recorded the proposed details for ${formVal.event.name}.`,
|
||||
`We've recorded the proposed details for ${formVal.event.name}.`
|
||||
);
|
||||
} else {
|
||||
formState.value = 'error';
|
||||
errors.push(
|
||||
'We were unable to record the proposed details for ' +
|
||||
formVal.event.name +
|
||||
". Poke Jonathan and tell him it's broken.",
|
||||
". Poke Jonathan and tell him it's broken."
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,7 @@
|
||||
</label>
|
||||
<label>
|
||||
<span>Date and time</span>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="date"
|
||||
v-model="formVal.event.date"
|
||||
/>
|
||||
<input type="date" name="date" v-model="formVal.event.date" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Department / Event Type</span>
|
||||
|
@ -1,7 +0,0 @@
|
||||
const fs = require('fs');
|
||||
|
||||
process.env.VUE_APP_HFF_ENTRY_FORMS_VERSION = require('./package.json').version;
|
||||
|
||||
process.env.VUE_APP_HFF_ENTRY_FORMS_GIT_HASH = JSON.stringify(
|
||||
require('git-describe').gitDescribeSync('.')
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user