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 @@ +hb logo \ 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) + } +}