Teamvite is an web app I wrote to manage my recreational sports teams. It supports sending reminders (email or sms) and tracking responses. Most views are mobile-optimized for ease of use.
Players can also download an ical calendar that has all the games and updates automatically.
I chose to write Teamvite in Go because of its low resource requirements compared to python or ruby and because I wanted more experience with Go. Note that this is actually v2 of this app, the first being picklespears (written in ruby).
This is the first app I've written in Go, so there are things I've likely missed. Feel free to open an issue with feedback on things I could do better. Thanks!
- simple deployment, development, and dependency processes
- mobile and desktop friendly
- low resource requirements
- high performance - performance is a feature
- reasonable security
- trade features for simpler design
- minimal dependencies
Teamvite is statically compiled using musl with sqlite bindings. (see bin/build.sh)
A faux fs is created to store all the text templates within the binary itself (see templates.go)
Deploys are done by copying the binary, moving the symlink and restarting the webserver. (see bin/deploy.sh)
The only library dependencies are ones that I don't feel qualified to write (bcrypt and sqlite3 bindings) or are very similar to any implementation I would write (uniuri).
Teamvite can be run without a proxy webserver (ex. nginx) in front of it and will deliver static files.
System module/package design follows ideas proposed in Standard-Package-Layout and Style-guidelines-for-Go-Packages
Packages:
- cmd/teamvite: teamvite binary. entry point into the app
- http: anything related to sending or receiving http traffic. html templates
- sqlite: anything that interacts with the sqlite database
- teamvite: anything specific to the teamvite app, domain objects, and interfaces
- reminders: send game reminders via email and SMS
This follows recommendations from Ben Johnson on organizing Go modules and Ted Kaminski on using modules to hide implementation details.
Teamvite is an open system. A logged in user can see other teams and players on those teams, but unless they are a manager or looking at themselves, they shouldn't be able to make changes.
In the http package, the request context will contain the domain type based on the current route. So if the url includes /team/ the context will include a Team struct in it. Same for players and games.
Documentation for common actions. Mostly a reference for me.
-
Insert new season in seasons table
INSERT INTO seasons (name) VALUES ('2022-winter');
-
Copy team placement email into file (ex 2022-winter-placements.txt) File should be formatted like:
<div_name> <team> . . <blank_line> <div_name>
-
Save new games once schedule is out (on laptop)
SEASON=summer bin/pdx_indoor_schedule.py $SEASON 2022
-
Make sure there any naming differences are captured
env SEASON=summer /bin/bash -c 'diff -u <(cut -f3 -d\| pi_games-$SEASON-2022.txt |sort -u) <(sort 2022-$SEASON-placements.txt)' |less
-
Update teams and divisions from placements file Note: has to be run from digitalocean droplet
./pdx_indoor_team_placements.py \ 2022-$SEASON-placements.txt \ --db /var/www/teamvite/teamvite.db
-
Upload games to teamvite.com (on laptop)
env APP_URL=https://teamvite.com bin/upload_schedule.rb \ pi_games-$SEASON-2022.txt |tee upload_schedule.log
-
Email new schedule to team
-
Profit!
bin/build.sh && bin/deploy.sh
- Install MailHog
- start mailhog
MailHog
- check config.json is set to localhost port 1025
- send reminders:
curl --silent http://teamvitedev.com:8080/send_game_reminders -H 'Content-Type: "application/json"' |jq
- view them on mailhog
http://0.0.0.0:8025/
docs on post body https://www.twilio.com/docs/messaging/guides/webhook-request
curl -i -X POST --silent \
http://teamvitedev.com:8080/sms \
-H "Content-Type: application/x-www-form-urlencoded" \
--data 'From=%2B19715347840&Body=yessir'
curl -i -X POST --silent \
http://teamvitedev.com:8080/sms \
-H "Content-Type: application/x-www-form-urlencoded" \
--data 'From=%2B19715347840&Body=nope'
curl -i -X POST --silent \
http://teamvitedev.com:8080/sms \
-H "Content-Type: application/x-www-form-urlencoded" \
--data 'From=%2B19715347840&Body=maybe?'
curl -i -X POST --silent \
http://teamvitedev.com:8080/sms \
-H "Content-Type: application/x-www-form-urlencoded" \
--data 'From=%2B19715347840&Body=STOP'
curl -i -X POST --silent \
http://teamvitedev.com:8080/sms \
-H "Content-Type: application/x-www-form-urlencoded" \
--data 'From=%2B19715347840&Body=stop'
curl -i -X POST --silent \
http://teamvitedev.com:8080/sms \
-H "Content-Type: application/x-www-form-urlencoded" \
--data 'From=1234&Body=stop'
curl -i -X POST --silent \
http://teamvitedev.com:8080/sms \
-H "Content-Type: application/x-www-form-urlencoded" \
--data 'Body=stop'
./teamvite resetpassword [email] [password]