diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2d09015
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.service
+build/
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0d5953f
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Kevin Stock
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..40bcf33
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,26 @@
+build: bindata-assetfs
+ go build
+
+bindata-assetfs:
+ go-bindata-assetfs assets
+
+run:
+ go-bindata-assetfs -debug assets
+ go run *.go
+
+install: bindata-assetfs
+ go install
+
+osx: bindata-assetfs
+ env GOOS=darwin GOARCH=amd64 go build -o build/hackbellingham-darwin_amd64
+ env GOOS=darwin GOARCH=386 go build -o build/hackbellingham-darwin_386
+
+linux: bindata-assetfs
+ env GOOS=linux GOARCH=amd64 go build -o build/hackbellingham-linux_amd64
+ env GOOS=linux GOARCH=386 go build -o build/hackbellingham-linux_386
+
+freebsd: bindata-assetfs
+ env GOOS=freebsd GOARCH=amd64 go build -o build/hackbellingham-freebsd_amd64
+ env GOOS=freebsd GOARCH=386 go build -o build/hackbellingham-freebsd_386
+
+all: osx linux freebsd
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9f3fbd4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,70 @@
+# HackBellingham.com
+
+## About Hack Bellingham
+Hack Bellingham is a social group dedicated to growing the local developer community.
+
+We are committed to providing a friendly, safe and welcoming environment for experienced and aspiring technologists, regardless of age, disability, gender, nationality, race, religion, sexuality, or similar personal characteristic.
+
+## About
+The primary purpose of this site is to automate the process for members to join the Hack Bellingham Slack team.
+
+## Building and Running
+The website is built using [Go][go] and is 'go gettable'. Once you have your go environment setup you can get dependencies by running:
+```sh
+go get
+```
+
+Once you have dependencies installed you can build for your current platform by running:
+```sh
+make
+```
+
+To run for development purposes run:
+```sh
+make run
+```
+
+To cross-compile to another platform run one of the following:
+```sh
+make linux
+make freebsd
+make osx
+```
+
+## Running (in Production)
+Running the site will require the setting of a number of options. These options can be set via command line flags or environment variables.
+
+| Environment Variable | Flag | Description | Default Value |
+|--------------------------------|-----------------|------------------------------------------------------------------------------------------|---------------|
+| `$HACK_BELLINGHAM_PORT` | `--port` | The TCP port to listen on. | `3000` |
+| `$HACK_BELLINGHAM_HOST` | `--host` | The IP address/hostname to listen on. | All hosts |
+| `$HACK_BELLINGHAM_SLACK_TEAM` | `--slack-team` | Slack team name, as found in the slack URL. | `""` |
+| `$HACK_BELLINGHAM_SLACK_TOKEN` | `--slack-token` | Access token for your slack team. It can be generated at https://api.slack.com/web#auth. | `""` |
+
+### systemd Configuration
+The canonical way to run the site is through the [`systemd`][systemd] service manager to setup the environment, manage when the application is started, and monitor the process to keep it running. This can be done with a system file like the one below:
+
+. The following
+
+```apacheconf
+[Unit]
+Description=Hack Bellingham Website
+
+[Service]
+ExecStart=/usr/local/bin/hackbellingham
+Restart=always
+User=root
+StandardOutput=syslog
+StandardError=syslog
+SyslogIdentifier=hackbellingham
+Environment=HACK_BELLINGHAM_PORT=80
+Environment=HACK_BELLINGHAM_SLACK_TEAM=hackbellingham
+Environment=HACK_BELLINGHAM_SLACK_TOKEN=XXXX-XXXXXXXXXXX-XXXXXXXXXXX-XXXXXXXXXXX-XXXXXXXXXX
+
+[Install]
+WantedBy=multi-user.target
+```
+
+
+[go]: http://www.golang.org
+[systemd]: https://freedesktop.org/wiki/Software/systemd/
diff --git a/assets/index.html b/assets/index.html
new file mode 100644
index 0000000..5bec464
--- /dev/null
+++ b/assets/index.html
@@ -0,0 +1,146 @@
+
+
+
+ Hack Bellingham
+
+
+
+
+
+
+
+
+
+
+
+
Hack Bellingham is a social group dedicated to growing the local developer community.
+
+
We are committed to providing a friendly, safe and welcoming environment for experienced and aspiring technologists, regardless of age, disability, gender, nationality, race, religion, sexuality, or similar personal characteristic.
+
+
+
+
+
Join us on Slack:
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/logo.svg b/assets/logo.svg
new file mode 100644
index 0000000..1419768
--- /dev/null
+++ b/assets/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/bindata_assetfs.go b/bindata_assetfs.go
new file mode 100644
index 0000000..1e63955
--- /dev/null
+++ b/bindata_assetfs.go
@@ -0,0 +1,270 @@
+// Code generated by go-bindata.
+// sources:
+// assets/index.html
+// assets/logo.svg
+// DO NOT EDIT!
+
+package main
+
+import (
+ "github.com/elazarl/go-bindata-assetfs"
+ "bytes"
+ "compress/gzip"
+ "fmt"
+ "io"
+ "strings"
+ "os"
+ "time"
+ "io/ioutil"
+ "path/filepath"
+)
+
+func bindataRead(data []byte, name string) ([]byte, error) {
+ gz, err := gzip.NewReader(bytes.NewBuffer(data))
+ if err != nil {
+ return nil, fmt.Errorf("Read %q: %v", name, err)
+ }
+
+ var buf bytes.Buffer
+ _, err = io.Copy(&buf, gz)
+ clErr := gz.Close()
+
+ if err != nil {
+ return nil, fmt.Errorf("Read %q: %v", name, err)
+ }
+ if clErr != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+type asset struct {
+ bytes []byte
+ info os.FileInfo
+}
+
+type bindataFileInfo struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+}
+
+func (fi bindataFileInfo) Name() string {
+ return fi.name
+}
+func (fi bindataFileInfo) Size() int64 {
+ return fi.size
+}
+func (fi bindataFileInfo) Mode() os.FileMode {
+ return fi.mode
+}
+func (fi bindataFileInfo) ModTime() time.Time {
+ return fi.modTime
+}
+func (fi bindataFileInfo) IsDir() bool {
+ return false
+}
+func (fi bindataFileInfo) Sys() interface{} {
+ return nil
+}
+
+var _assetsIndexHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\x57\x6b\x53\xe3\xba\x19\xfe\x9c\xfd\x15\xaa\x3b\x67\x92\x4c\x13\xe7\x02\x2c\x10\x62\xda\x9e\xed\xe9\xb0\x67\xf6\x36\x0b\x9d\xf6\x7c\x54\xec\x37\xb6\x0e\xb2\xe5\x4a\x72\x42\xda\xe1\xbf\xf7\x91\xe4\x84\x24\xb0\x94\x9d\x25\xc4\xef\xfd\x79\xaf\x66\xfe\x87\xbf\x7d\xfd\x70\xf7\xdb\xb7\x5f\x58\x61\x4b\x79\xfd\x6e\xbe\xfd\x45\x3c\xbb\x7e\xd7\x99\x5b\x61\x25\x5d\xdf\xf0\xf4\x9e\xfd\x4c\x52\x8a\x2a\x2f\x78\x39\x1f\x05\x32\xf8\xa0\xdc\xb3\x42\xd3\x32\xe9\x16\xd6\xd6\x66\x36\x1a\x2d\x55\x65\x4d\x9c\x2b\x95\x4b\xe2\xb5\x30\x71\xaa\xca\x51\x6a\xcc\x9f\x97\xbc\x14\x72\x93\x7c\x57\x0b\x65\xd5\x9f\x6e\x25\x5f\xcc\x4e\xc7\xe3\xc1\xc9\x78\xdc\x65\x9a\x64\xd2\x35\x76\x23\xc9\x14\x44\xb6\xcb\xec\xa6\xa6\xa4\x6b\xe9\xc1\x3a\xdd\xae\xf3\x55\x92\xe5\xac\xe2\x25\x25\xd1\x4a\xd0\xba\x56\xda\x46\x2c\x85\x37\xaa\x6c\x12\xad\x45\x66\x8b\x24\xa3\x95\x48\x69\xe8\x1f\x06\x4c\x54\xc2\x0a\x2e\x87\x26\xe5\x92\x92\x49\xe4\xac\x78\x27\xc1\x7c\xb4\x35\xef\x18\x1d\x87\x7c\xc0\x16\x2a\xdb\xb0\xff\xe2\xb1\x53\xf2\x87\x60\x67\xc6\x2e\x4e\xc7\xf5\xc3\x55\x20\xea\x5c\x54\x33\x36\x66\xbc\xb1\xca\x93\x6a\x9e\x65\xc8\xcb\x8c\x4d\x20\xc4\xa6\xdb\x0f\xcf\x73\xb9\x18\x06\xdc\x33\xd6\x0d\xc8\x99\x43\xde\x1d\xb0\xbf\x6a\x84\x36\x60\x86\xb4\x58\x5e\xb1\x9d\xf4\x9a\x44\x5e\xd8\x19\x43\x5a\x9e\x4c\x18\xf1\x1f\x02\xe9\x7d\x6b\x76\x81\x7a\xe4\x5a\x35\x55\x36\x4c\x95\x54\x7a\xc6\xd6\x85\xb0\xe4\x79\x2d\x41\xe7\x8b\xde\x74\x3c\xf0\xff\xfb\x8e\xf1\x88\x9f\x3f\x4a\x95\xab\x80\xae\x45\x86\x02\xec\x90\xed\xe0\x5e\x9e\xfe\x74\x00\x36\x3e\xa3\x92\x8d\xb7\x46\x8a\xc9\x80\x15\xd3\x4e\x30\x73\x10\xf2\xe9\xf3\x90\x27\x54\x3e\x23\xe9\x40\xf3\xb6\xa6\x2f\x98\x69\x91\x3b\xbe\xa8\xea\xc6\xa2\x28\x8d\xb5\xaa\xda\x13\x0d\x96\xe2\x8b\xe7\xd6\xe3\x0b\xbd\x4f\x7c\x6b\xee\x77\xce\x0e\xb2\x73\x79\x12\x4f\xcf\x7e\x3a\xac\xf2\xd4\x91\xd8\x24\x9e\xb4\x1c\xa7\xb9\x1f\xdf\x42\xe9\x8c\xf4\x50\xf3\x4c\x34\x06\x68\x5d\xf2\xae\xf6\x18\xbe\xff\x66\xac\x52\xd5\x41\xbd\x9e\x0a\xf8\xbc\xb8\xae\x96\xef\xc7\x03\x36\x39\x47\xea\x2f\x2f\xfb\x7b\xc5\x19\x5a\x55\x87\x02\x1d\x46\xe9\x4b\xe6\x93\xef\xc8\x56\xf3\xca\x60\x14\x14\x8a\xe9\xa1\xb1\xf8\xfc\xcc\xa0\x8c\x3e\xe3\xed\xc3\xb1\x5f\x4f\x3e\x44\x38\x2b\xd4\x8a\x74\xc0\x99\x36\xda\xb8\xe0\x6a\x25\x30\x81\xfa\x48\x30\x13\x86\x2f\x24\x65\x6d\x4e\x5e\x82\xc4\x7b\x67\x27\x83\xf3\xd3\xc1\xe5\xc9\x20\x3e\x0b\x98\xb6\x36\xd7\x5c\xd8\x60\x10\x1f\xb1\x69\xd2\x94\x0c\x22\x8c\x97\x5c\xc8\x46\xd3\xab\x46\xf7\x12\x05\x8d\x69\x7f\x2f\xf7\xa8\x06\x86\xd3\x28\x29\xb2\x97\x73\xfa\x5a\xbe\x77\xa9\x1d\x6f\x9b\x3a\x44\xf7\xff\x63\xea\x4d\x4f\x61\x67\x3a\x9d\xfa\x8f\xfd\x80\xf6\x85\x26\x97\x67\xe0\x23\xe6\xf3\xf1\xb3\x60\x8e\x79\xde\xf3\x5f\x4a\xca\x04\x67\xbd\xa7\xc1\x7d\xef\x86\xb9\x1f\x22\x39\xde\x66\xfb\x33\x32\xbd\x68\x67\xfe\xf1\xc7\xb6\x4e\xdf\x6a\x6b\x7c\x64\x6b\x3e\xf2\x1d\x8e\x1b\x32\x0a\x47\x64\xee\x14\xdd\xe6\x75\x8f\xa4\xdd\xaa\x9d\x8b\x32\x67\x22\x4b\x22\xb7\x8f\x22\x66\x74\x1a\xbe\xc6\x66\x95\xfb\x25\x3d\xda\xca\xba\xb5\xcf\x45\xe5\x94\x9c\x5e\x31\x39\xbe\x45\x4c\x18\xc6\x51\xd3\x14\x03\xcd\x5c\xea\x6b\x96\x01\x4b\xca\x2d\x9a\x0f\x03\x0f\xd2\x1a\x92\xcc\x16\xc4\xa4\xc2\x1d\x00\x7b\x45\x52\xd5\xe8\x63\x1c\xa6\xb2\xc1\x89\xd8\xc4\x70\x38\x71\xce\x3a\xf3\xfa\xfa\x9f\xc4\x38\xea\xe9\x98\xc2\xb6\x56\x6a\xad\x56\xc2\x95\x1f\xbe\x96\x5a\x50\x95\xc9\x0d\xd6\x07\x5f\x42\xb6\xca\xd8\x9a\x24\xc4\x1d\x9b\xaa\x95\xd0\xaa\x2a\x71\x92\xd8\x12\x33\x44\x0f\x70\x04\xf9\x14\x76\x9c\x24\x37\xb5\xd0\x3e\x1e\x4a\x8b\x0a\x15\xce\x85\xb1\x68\x6d\x4d\x39\xd7\x19\xae\x9f\x61\x6a\xc9\x78\x4e\x03\xe6\x47\x48\x48\x84\x37\x60\x39\x3c\x92\x1e\xe0\xfa\xb9\x21\xe6\x81\xa8\x79\x4a\x4e\x53\x8a\x1c\x44\xb7\xcd\x1e\x9a\x96\x05\xcf\x46\x60\xf3\x71\xcd\xe0\xdf\x38\x15\x96\x16\x1c\x1a\x18\x55\x78\x14\x29\x20\xd7\x01\x71\x26\x56\xbe\x16\xc6\x72\xdb\x98\x61\x89\x18\xe0\x3f\xba\x9e\x8f\xc0\x39\x12\x91\x48\xfe\x10\xb8\x4a\x7f\x31\x51\x8f\xe9\xf5\xaf\x98\x7f\xd6\x20\xec\xca\x2d\xd7\xf4\x7e\x86\x5c\x4e\x03\x37\x2c\xd4\xa7\x53\x1b\xb1\x1a\x12\x54\x28\x09\x30\x49\x44\xa8\xac\x64\x18\x2b\x0d\x97\x91\x77\xe0\x49\xc3\x1d\xc9\xdd\xd8\xa5\x4a\x1b\x13\xec\xb5\x6b\x56\x55\xa9\x14\xe9\x7d\x12\x69\xfa\x77\x43\xc6\x7e\x44\xce\x2d\xf5\xfa\x57\xc1\x86\xf0\x8f\xc3\x20\x1c\x5d\xdf\x22\x75\xac\x74\x75\x62\x81\x33\x1f\x05\x96\x6f\xc4\x2d\xc6\xf9\x28\xb4\x99\x7b\x41\x48\xb5\xa8\xf7\xc3\x1e\xfd\xce\x57\x3c\x50\x3d\xec\x65\x53\xa5\xae\x0c\xec\xc8\x7f\x18\x8e\x15\x72\xde\x06\x9a\xb0\x0c\xc1\xbb\x5e\x88\x73\xb2\xbf\x48\x72\x5f\x7f\xde\x7c\xcc\x7a\x47\x41\xf6\x07\x4e\x93\xe1\x9f\xd2\x28\x26\xaa\xf5\x89\x2f\x48\xc2\x40\x10\x88\x45\x55\x91\xbe\xb9\xfb\xfc\x29\xec\xf3\x96\xba\x5b\xb2\x09\xb3\xba\x69\xaf\xc7\x91\x02\x78\xd1\xf7\x10\xa7\x6f\xdf\x6d\x16\xe2\x38\x8e\x82\x31\x17\xf0\x43\xa1\x21\x58\xd1\x9a\xfd\xeb\xf3\xa7\x1b\xbc\xc8\xb5\x2a\xbd\xb0\x8a\xc0\x8e\x31\x31\x55\xaf\xfb\xed\xeb\xed\x1d\xee\x27\x1a\x5a\x65\xf4\x8f\xef\x1f\x7b\xdd\x36\x09\xc3\x60\xb6\xdb\x7f\xd2\x30\x64\x5b\x33\x37\x7e\x9a\x7b\xdd\x0f\xe1\x65\x6d\x78\x87\xdc\xc2\x4a\x97\xd7\xb5\x74\xa3\x8a\x64\x8e\x7e\x47\x97\x76\xf7\x95\xab\xac\xf7\xeb\xed\xd7\x2f\xb1\xb1\x6e\x60\xc4\x72\xd3\x0b\xcb\xc7\xf7\xc8\xec\xc7\xa9\x3d\xec\xa1\x7e\xbc\xe2\xb2\x21\xbf\xa1\x5c\x6c\xef\x76\x78\x2a\xa9\xb8\x4b\xdd\xb6\x9c\xdb\x02\xfa\x84\x84\x61\xf8\x1c\x66\xe1\xb5\x42\x1e\x4d\x4d\xa8\x64\xa7\xe3\x27\xe5\xef\x18\x94\x57\x75\x9f\xc6\xa9\x1f\x6a\xd1\x11\x4b\xd6\xf3\xf0\xbd\x59\x96\x24\x09\x36\xec\x78\x1b\x59\xe7\x20\xac\x38\x95\xdc\x98\x2f\x78\x21\x76\x45\x6e\xaf\x64\xf4\x92\xe0\x41\x37\x60\xc5\xfd\xa6\x1a\x1d\xfa\xc0\xe7\x9e\x15\xdc\xb0\x05\x51\x85\x15\x82\x30\xd9\x37\xbc\xb8\x1b\x6c\xc0\x82\xb0\x6a\x37\x4e\xd6\xe7\xd4\xaf\x8c\xb6\x69\xf6\x10\xc6\x35\xd6\x65\x65\xbf\xa0\x21\x62\xbc\x7b\xe1\x0d\xe1\x43\x21\x64\xd6\xdb\x09\x6c\xb1\x3d\x86\xf2\x49\x43\x6f\x80\xd3\x9e\xd5\x37\xc1\xb9\x2b\x08\x0b\x7b\x0d\x10\x68\x6f\xd2\x1a\xdb\x0f\x0b\xdb\x65\xc3\xb5\xfc\xe6\x10\xec\x0e\x9e\xd5\x1b\x2c\x5a\x4c\x3d\x93\x38\x16\xfa\x10\xdd\xf3\x09\x5b\x72\x04\x7e\x75\xc0\xdd\x8f\xe3\x60\x70\xaf\x9e\xf0\x3e\x1e\x76\xf4\xf6\x55\x1c\x57\xd2\x2f\x14\xec\x13\x1c\xca\x70\x21\xb1\x38\xdd\x1f\x5f\xff\x0b\x00\x00\xff\xff\xec\x5f\x86\x74\x93\x0d\x00\x00")
+
+func assetsIndexHtmlBytes() ([]byte, error) {
+ return bindataRead(
+ _assetsIndexHtml,
+ "assets/index.html",
+ )
+}
+
+func assetsIndexHtml() (*asset, error) {
+ bytes, err := assetsIndexHtmlBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "assets/index.html", size: 3475, mode: os.FileMode(420), modTime: time.Unix(1456160915, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+var _assetsLogoSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x64\x53\xcd\x4e\xe3\x30\x10\x7e\x95\x28\x7b\xb5\x27\xf6\xd8\x1e\xdb\xa8\x45\xda\x3d\x71\xe8\x1e\x90\x90\x0f\xdc\xdc\x25\xd0\x4a\x81\x52\x88\x28\xbc\xfd\x8e\xed\x48\x40\x51\xa4\x91\x33\xff\xdf\x7c\x33\xab\xd7\xb7\x87\x6e\x7f\xb7\xee\xa3\x57\x5b\xe3\xd0\xc8\x18\xcc\x3f\x69\xf3\x56\xc9\x1c\x8d\x97\xc6\x7b\x7f\x3f\x7a\x47\xd6\xa9\xbe\xbb\xcb\x73\x96\x4f\xf9\x71\x5c\xf7\x9b\xfc\x31\xbe\x74\xba\xef\xde\x1f\xa7\xa7\xd7\x75\xbf\x9b\xe7\xe7\x8b\x61\x38\x9d\x4e\x70\x32\x70\x78\x79\x18\x50\x29\x35\x70\xfe\xbe\x7b\xdb\x8f\xa7\x3f\x87\xf7\x75\xaf\x3a\xd5\xe9\xa8\xc0\x87\x8e\x0c\xb8\xd0\x5f\xae\xe6\xfd\x3c\x8d\x97\xbb\x6d\x37\x1d\x1e\x0e\xab\xa1\xfd\xae\x9e\xf3\xbc\xeb\xb8\xad\xbf\x16\xc1\x0a\xed\x39\x62\x92\x46\x43\x88\x42\xdb\x4d\x55\x5a\x07\xde\x27\x17\x36\x4a\x18\x0f\x2e\x26\xa4\x66\x70\x60\x29\xd5\x90\xdb\xbe\x7b\x9d\x3f\x26\xee\xf6\x7e\x3f\x4d\x17\xbf\x8c\xb5\xd1\x8d\xfd\xf0\x25\x7f\xac\x39\x8d\x01\x74\x89\x38\x1a\xaf\x82\x02\xc4\x64\x1c\x28\x3a\x2a\x69\x21\x38\xa9\xc1\x04\x49\x10\xed\x6f\x27\x9c\x50\xf5\xf3\x16\x0c\x0a\xa4\xec\x80\x58\x5b\x44\x35\x48\x27\x10\x82\xb9\x26\xee\x49\x70\xc7\x14\x45\x7b\x12\xf8\xa5\x82\x73\xec\x90\xd4\x55\xd1\x27\xb4\x80\x26\x6b\x05\x11\x45\x93\x25\x8d\x16\xac\x76\x92\x21\x06\xa1\x11\xa8\x89\x66\xf1\x80\x24\xf1\x48\xdc\x14\xff\x47\x20\x53\x9c\xcd\xcd\x17\x28\xe7\xc0\x31\x06\xbd\xdd\x7e\x03\xae\x55\x04\xcd\x00\x0c\x04\x9f\x35\x81\xf6\xa2\xc9\x56\xa4\xd4\xe6\xaa\xdc\x3f\x2e\xb2\xe9\x09\x6c\xe0\x79\x04\x7f\x2c\xc5\xb9\x2b\x64\x7c\xa0\x6f\x34\x73\x80\x4c\x8a\x3a\xaa\xe2\xcf\x8b\x54\x00\x14\xe6\xac\xce\xda\x00\x31\x8e\x2a\x6b\x1a\xc9\x95\x39\xaa\x61\xc3\x45\x36\x8b\xe7\xb9\x4b\x2c\xce\xc1\x2e\xb2\x19\x2a\x13\x3c\x39\x7a\x23\x20\xba\x8a\x25\x73\x52\x3b\x2e\xe6\x75\xaa\x30\x6e\x1f\x19\x81\xa9\x38\x50\x33\x77\x04\x4e\x22\x43\x92\x3c\x57\x1d\x33\x15\x8e\xe8\x93\x28\x92\xa6\xb2\x03\xde\x36\xb1\xa8\x45\x51\x1f\x39\x12\x75\x7d\xb6\x57\xcd\x31\x7f\x3e\xf3\x59\x9c\x68\x71\xe2\xac\x8a\x68\x55\xae\x35\xf2\x5a\x46\x61\xb9\x27\x6e\x70\xf9\x51\xa0\xfc\x0f\xaa\x5c\x1e\xdd\xfd\x77\xaa\x6c\x28\x5c\xd7\x95\x4e\x65\xbb\x37\xed\x86\x78\xfb\x52\x5d\xfd\xcd\xe2\xe1\x42\xaa\x57\x31\xd5\x4b\x91\x3c\xbd\xf8\x63\x15\x3e\x6f\xa0\xdc\xe6\xe5\xff\x00\x00\x00\xff\xff\xba\x35\x64\xfb\x01\x04\x00\x00")
+
+func assetsLogoSvgBytes() ([]byte, error) {
+ return bindataRead(
+ _assetsLogoSvg,
+ "assets/logo.svg",
+ )
+}
+
+func assetsLogoSvg() (*asset, error) {
+ bytes, err := assetsLogoSvgBytes()
+ if err != nil {
+ return nil, err
+ }
+
+ info := bindataFileInfo{name: "assets/logo.svg", size: 1025, mode: os.FileMode(420), modTime: time.Unix(1455944824, 0)}
+ a := &asset{bytes: bytes, info: info}
+ return a, nil
+}
+
+// Asset loads and returns the asset for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func Asset(name string) ([]byte, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
+ }
+ return a.bytes, nil
+ }
+ return nil, fmt.Errorf("Asset %s not found", name)
+}
+
+// MustAsset is like Asset but panics when Asset would return an error.
+// It simplifies safe initialization of global variables.
+func MustAsset(name string) []byte {
+ a, err := Asset(name)
+ if (err != nil) {
+ panic("asset: Asset(" + name + "): " + err.Error())
+ }
+
+ return a
+}
+
+// AssetInfo loads and returns the asset info for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func AssetInfo(name string) (os.FileInfo, error) {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ if f, ok := _bindata[cannonicalName]; ok {
+ a, err := f()
+ if err != nil {
+ return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
+ }
+ return a.info, nil
+ }
+ return nil, fmt.Errorf("AssetInfo %s not found", name)
+}
+
+// AssetNames returns the names of the assets.
+func AssetNames() []string {
+ names := make([]string, 0, len(_bindata))
+ for name := range _bindata {
+ names = append(names, name)
+ }
+ return names
+}
+
+// _bindata is a table, holding each asset generator, mapped to its name.
+var _bindata = map[string]func() (*asset, error){
+ "assets/index.html": assetsIndexHtml,
+ "assets/logo.svg": assetsLogoSvg,
+}
+
+// AssetDir returns the file names below a certain
+// directory embedded in the file by go-bindata.
+// For example if you run go-bindata on data/... and data contains the
+// following hierarchy:
+// data/
+// foo.txt
+// img/
+// a.png
+// b.png
+// then AssetDir("data") would return []string{"foo.txt", "img"}
+// AssetDir("data/img") would return []string{"a.png", "b.png"}
+// AssetDir("foo.txt") and AssetDir("notexist") would return an error
+// AssetDir("") will return []string{"data"}.
+func AssetDir(name string) ([]string, error) {
+ node := _bintree
+ if len(name) != 0 {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ pathList := strings.Split(cannonicalName, "/")
+ for _, p := range pathList {
+ node = node.Children[p]
+ if node == nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ }
+ }
+ if node.Func != nil {
+ return nil, fmt.Errorf("Asset %s not found", name)
+ }
+ rv := make([]string, 0, len(node.Children))
+ for childName := range node.Children {
+ rv = append(rv, childName)
+ }
+ return rv, nil
+}
+
+type bintree struct {
+ Func func() (*asset, error)
+ Children map[string]*bintree
+}
+var _bintree = &bintree{nil, map[string]*bintree{
+ "assets": &bintree{nil, map[string]*bintree{
+ "index.html": &bintree{assetsIndexHtml, map[string]*bintree{
+ }},
+ "logo.svg": &bintree{assetsLogoSvg, map[string]*bintree{
+ }},
+ }},
+}}
+
+// RestoreAsset restores an asset under the given directory
+func RestoreAsset(dir, name string) error {
+ data, err := Asset(name)
+ if err != nil {
+ return err
+ }
+ info, err := AssetInfo(name)
+ if err != nil {
+ return err
+ }
+ err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
+ if err != nil {
+ return err
+ }
+ err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// RestoreAssets restores an asset under the given directory recursively
+func RestoreAssets(dir, name string) error {
+ children, err := AssetDir(name)
+ // File
+ if err != nil {
+ return RestoreAsset(dir, name)
+ }
+ // Dir
+ for _, child := range children {
+ err = RestoreAssets(dir, filepath.Join(name, child))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func _filePath(dir, name string) string {
+ cannonicalName := strings.Replace(name, "\\", "/", -1)
+ return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
+}
+
+
+func assetFS() *assetfs.AssetFS {
+ for k := range _bintree.Children {
+ return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: k}
+ }
+ panic("unreachable")
+}
diff --git a/helpers.go b/helpers.go
new file mode 100644
index 0000000..582c471
--- /dev/null
+++ b/helpers.go
@@ -0,0 +1,17 @@
+// Helpers
+//
+// Misc. helper functions
+
+package main
+
+import (
+ "encoding/json"
+ "net/http"
+)
+
+func jsonResponse(w http.ResponseWriter, body interface{}, status int) {
+ bytes, _ := json.Marshal(body)
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(status)
+ w.Write(bytes)
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..e8c5ec2
--- /dev/null
+++ b/main.go
@@ -0,0 +1,55 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+ "os"
+
+ "github.com/codegangsta/cli"
+)
+
+type handleFunc func(w http.ResponseWriter, req *http.Request)
+
+func main() {
+ app := cli.NewApp()
+ app.Version = "1.0.2"
+ app.Usage = "hack bellingham website"
+
+ app.Flags = []cli.Flag{
+ cli.IntFlag{
+ Name: "port",
+ Value: 3000,
+ EnvVar: "HACK_BELLINGHAM_PORT",
+ Usage: "tcp port to listen on",
+ },
+ cli.StringFlag{
+ Name: "host",
+ Value: "",
+ EnvVar: "HACK_BELLINGHAM_HOST",
+ Usage: "ip address/host to listen on",
+ },
+ cli.StringFlag{
+ Name: "slack-team",
+ EnvVar: "HACK_BELLINGHAM_SLACK_TEAM",
+ Usage: "",
+ },
+ cli.StringFlag{
+ Name: "slack-token",
+ EnvVar: "HACK_BELLINGHAM_SLACK_TOKEN",
+ Usage: "",
+ },
+ }
+
+ app.Action = serve
+
+ app.Run(os.Args)
+}
+
+func serve(c *cli.Context) {
+ http.HandleFunc("/request-invite", slackInviteRequestHandler(c))
+ http.HandleFunc("/status", statusHandler(c))
+ http.Handle("/", http.FileServer(assetFS()))
+
+ addr := fmt.Sprintf("%s:%d", c.String("host"), c.Int("port"))
+ http.ListenAndServe(addr, nil)
+}
diff --git a/slack.go b/slack.go
new file mode 100644
index 0000000..e64ea70
--- /dev/null
+++ b/slack.go
@@ -0,0 +1,57 @@
+// Slack
+//
+// Provides a HTTP handler for signing up for a Slack invite
+// via a JSON request.
+
+package main
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/codegangsta/cli"
+ "github.com/nlopes/slack"
+)
+
+type invitationRequest struct {
+ Email string `json:"email"`
+}
+
+type invitationResponse struct {
+ Message string `json:"message"`
+ ErrorCode int `json:"errorCode,omitempty"`
+}
+
+func slackInviteRequestHandler(c *cli.Context) handleFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+
+ decoder := json.NewDecoder(req.Body)
+ var invitation invitationRequest
+ err := decoder.Decode(&invitation)
+ if err != nil {
+ JsonResponse(w, invitationResponse{
+ Message: "There was an error processing your invitation. Please try again later.",
+ ErrorCode: 1,
+ }, http.StatusInternalServerError)
+ return
+ }
+
+ err = inviteToSlack(c.String("slack-token"), c.String("slack-team"), "", "", invitation.Email)
+ if err != nil {
+ JsonResponse(w, invitationResponse{
+ Message: "There was an error processing your invitation. Please try again later.",
+ ErrorCode: 2,
+ }, http.StatusInternalServerError)
+ return
+ }
+
+ JsonResponse(w, invitationResponse{
+ Message: "Your invitation has been sent. Please check your email.",
+ }, http.StatusOK)
+ }
+}
+
+func inviteToSlack(slackToken, teamName, firstName, lastName, email string) error {
+ client := slack.New(slackToken)
+ return client.InviteToTeam(teamName, firstName, lastName, email)
+}
diff --git a/status.go b/status.go
new file mode 100644
index 0000000..16f7477
--- /dev/null
+++ b/status.go
@@ -0,0 +1,25 @@
+// Status
+//
+// Provides a HTTP handler for reporting the status of
+// the application in JSON format.
+
+package main
+
+import (
+ "net/http"
+
+ "github.com/codegangsta/cli"
+)
+
+type statusResponse struct {
+ Version string `json:"version"`
+}
+
+func statusHandler(c *cli.Context) handleFunc {
+ return func(w http.ResponseWriter, req *http.Request) {
+ response := statusResponse{
+ Version: c.App.Version,
+ }
+ JsonResponse(w, response, http.StatusOK)
+ }
+}