diff --git a/assets/templates/archetype.tmpl b/assets/templates/archetype.tmpl
index 00755de5959af3cc2baac9183a685766fc67255d..17e00da777e1ff4df79b187aae3b47404c98865d 100644
--- a/assets/templates/archetype.tmpl
+++ b/assets/templates/archetype.tmpl
@@ -60,7 +60,7 @@
                                                         class="fab fa-instagram"></i> Instagram</a></li>
                                         <li><a href="https://discord.gg/aovtr"> <i class="fab fa-discord"></i>
                                                 Discord</a>
-                                        <li><a href="{{.auth_ref}}"> <i class="fas fa-user"></i> {{.auth_text}}</a>
+                                        <li><a href="{{.auth_ref}}"> <i class="fas fa-user"></i> {{.auth_text}} (Click to log out)</a>
                                         </li>
                                     </ul>
                                 </div>
diff --git a/assets/templates/form.tmpl b/assets/templates/form.tmpl
index 8fca64f772830ef9830c135976e59d2e7fd3a950..5eb300e26092baa82839f88cc707ed21d4ba182d 100644
--- a/assets/templates/form.tmpl
+++ b/assets/templates/form.tmpl
@@ -12,56 +12,57 @@
                             <h2>{{.tournament.Title}} <span>FORMU</span></h2>
                         </div>
                         <div class="contact-form">
-                            <form action="#">
+                            <form method="post" action="">
                                 <div class="row">
                                     {{if .team_size.A}}
                                         <div class="col-md-12">
                                             <label>
-                                                <input name="teamName" type="text" placeholder="Takım Adınız">
+                                                <input type="text" placeholder="Takım Adınız" name="TeamName">
                                             </label>
                                         </div>
                                         <div class="col-md-6">
                                             <label>
-                                                <input type="text" placeholder="Takım Kaptanınız">
+                                                Takım Kaptanınız
+                                                <input type="text" placeholder="Takım Kaptanınız" value="{{.auth_text}}" disabled="disabled">
                                             </label>
                                         </div>
                                     {{end}}
                                     <div class="col-md-6">
                                         <label>
-                                            <input type="text" placeholder="Adınız">
+                                            <input type="text" placeholder="Adınız" name="FirstName">
                                         </label>
                                     </div>
                                     <div class="col-md-6">
                                         <label>
-                                            <input type="text" placeholder="Soyadınız">
+                                            <input type="text" placeholder="Soyadınız" name="LastName">
                                         </label>
                                     </div>
 
                                     {{if .team_size.A}}
                                         <div class="col-md-6">
                                             <label>
-                                                <input type="text" placeholder="Player 1 Name + UID">
+                                                <input type="text" placeholder="Player 1 Name + UID" name="Player0">
                                             </label>
                                         </div>
                                     {{ end }}
                                     {{if .team_size.B}}
                                         <div class="col-md-6">
                                             <label>
-                                                <input type="text" placeholder="Player 2 Name + UID">
+                                                <input type="text" placeholder="Player 2 Name + UID" name="Player1">
                                             </label>
                                         </div>
                                     {{ end }}
                                     {{if .team_size.C}}
                                         <div class="col-md-6">
                                             <label>
-                                                <input type="text" placeholder="Player 3 Name + UID">
+                                                <input type="text" placeholder="Player 3 Name + UID" name="Player2">
                                             </label>
                                         </div>
                                     {{ end }}
                                     {{if .team_size.D}}
                                         <div class="col-md-6">
                                             <label>
-                                                <input type="text" placeholder="Player 4 Name + UID">
+                                                <input type="text" placeholder="Player 4 Name + UID" name="Player3">
                                             </label>
                                         </div>
                                     {{ end }}
diff --git a/routes.go b/routes.go
index 87498ec92d274dbc6608bdb044bf1ac0b845ac97..bc09d6d5590ab9c952806d39619cc8de94567e2c 100644
--- a/routes.go
+++ b/routes.go
@@ -35,13 +35,24 @@ func registerRoutes() {
 			return
 		}
 
-		// TODO: consume user stuff here
-
 		t := contextTournament(context)
 		if t == nil {
 			return
 		}
 		populateTournament(t)
+		if t.EnrollmentOverdue {
+			context.Redirect(http.StatusTemporaryRedirect, "/")
+			return
+		}
+		if s, err := existsTeam(t.ID, user.ID); err != nil {
+			log.Printf("error querying team existance for team %s in tournament %s: %s",
+				user.ID, t.ID.String(), err)
+			context.String(http.StatusInternalServerError, "Internal Server Error")
+			return
+		} else if s {
+			context.Redirect(http.StatusTemporaryRedirect, "/")
+			return
+		}
 
 		authText, authRef := getAuthButton(user)
 
@@ -95,8 +106,48 @@ func registerRoutes() {
 			return
 		}
 
-		// TODO: handle form submit
-		context.String(http.StatusOK, "TODO")
+		t := contextTournament(context)
+		if t == nil {
+			return
+		}
+		populateTournament(t)
+		if t.EnrollmentOverdue {
+			context.Redirect(http.StatusTemporaryRedirect, "/")
+			return
+		}
+
+		if s, err := existsTeam(t.ID, user.ID); err != nil {
+			log.Printf("error querying team existance for team %s in tournament %s: %s",
+				user.ID, t.ID.String(), err)
+			context.String(http.StatusInternalServerError, "Internal Server Error")
+			return
+		} else if s {
+			context.Redirect(http.StatusFound, "/")
+			return
+		}
+
+		var tsn int
+		if n, err := strconv.Atoi(t.Attributes["size"]); err != nil {
+			log.Printf("error parsing team size of team %s: %s", t.ID.String(), err)
+			context.String(http.StatusInternalServerError, "Internal Server Error")
+			return
+		} else {
+			tsn = n
+		}
+
+		team := bindEnrollment(context, user, tsn)
+		if team == nil {
+			return
+		}
+		team.Tournament = t.ID
+
+		if err := putTeam(team); err != nil {
+			log.Printf("error pushing enrollment for tournament %s: %s", t.ID.String(), err)
+			context.String(http.StatusInternalServerError, "Internal Server Error")
+			return
+		}
+
+		context.Redirect(http.StatusFound, "/")
 	})
 
 	router.GET("/admin", func(context *gin.Context) {
diff --git a/tournament.go b/tournament.go
index 7b6c3d8f7537a9e0e2a91bdd346a01cca170c388..34e5f4fd2910db60d1252068b2a39042f8a80b42 100644
--- a/tournament.go
+++ b/tournament.go
@@ -107,12 +107,16 @@ func getTournaments() ([]*tournament, error) {
 	return tournamentsCache, nil
 }
 
+func getBytesTournament(id uuid.UUID) ([]byte, error) {
+	return id.MarshalBinary()
+}
+
 func getTournament(id uuid.UUID) (*tournament, []byte, error) {
 	tournamentLock.RLock()
 	defer tournamentLock.RUnlock()
 
 	var b []byte
-	if payload, err := id.MarshalBinary(); err != nil {
+	if payload, err := getBytesTournament(id); err != nil {
 		return nil, nil, err
 	} else {
 		b = payload
@@ -180,7 +184,7 @@ func makeTournament(t *tournament) error {
 
 func updateTournament(t *tournament) error {
 	var b []byte
-	if _, p, err := getTournament(t.ID); err != nil {
+	if p, err := getBytesTournament(t.ID); err != nil {
 		return err
 	} else {
 		b = p
@@ -204,7 +208,7 @@ func updateTournament(t *tournament) error {
 
 func destroyTournament(id uuid.UUID) error {
 	var b []byte
-	if _, p, err := getTournament(id); err != nil {
+	if p, err := getBytesTournament(id); err != nil {
 		return err
 	} else {
 		b = p
@@ -231,12 +235,12 @@ func contextTournament(context *gin.Context) *tournament {
 		if config.System.Verbose {
 			log.Printf("error parsing UUID %s: %s", context.Param("id"), err)
 		}
-		context.Redirect(http.StatusTemporaryRedirect, "/admin")
+		context.Redirect(http.StatusTemporaryRedirect, "/")
 		return nil
 	} else {
 		if t, _, err = getTournament(id); err != nil {
 			if isNotFound(err) {
-				context.Redirect(http.StatusTemporaryRedirect, "/admin")
+				context.Redirect(http.StatusTemporaryRedirect, "/")
 				return nil
 			}
 			log.Printf("error getting tournament %s: %s", id.String(), err)
diff --git a/user.go b/user.go
index 9626b9d6353b3c4c0348af41ef16b6e16d582880..774424be79ad3c5c87d0b79acba9ae07d6cce9a0 100644
--- a/user.go
+++ b/user.go
@@ -2,17 +2,229 @@ package main
 
 import (
 	"github.com/bwmarrin/discordgo"
-	"sync"
+	"github.com/gin-gonic/gin"
+	"github.com/google/uuid"
+	json "github.com/json-iterator/go"
+	"github.com/syndtr/goleveldb/leveldb"
+	"log"
+	"net/http"
 )
 
-var userLock = sync.RWMutex{}
+type userEnrollment struct {
+	TeamName  string
+	FirstName string
+	LastName  string
+
+	Player0 string
+	Player1 string
+	Player2 string
+	Player3 string
+}
+
+type teamPayload struct {
+	Name               string    `json:"name,omitempty"`
+	SubmitterFirstName string    `json:"submitter_first_name"`
+	SubmitterLastName  string    `json:"submitter_last_name"`
+	Captain            string    `json:"captain"`
+	Players            []string  `json:"players,omitempty"`
+	Tournament         uuid.UUID `json:"tournament"`
+}
+
+func putTeam(t *teamPayload) error {
+	var b []byte
+	if p, err := getBytesTournament(t.Tournament); err != nil {
+		return err
+	} else {
+		b = p
+	}
+
+	var enrollments []string
+	if data, err := userDB.Get(b, nil); err != nil && !isNotFound(err) {
+		return err
+	} else {
+		if data != nil {
+			if err = json.Unmarshal(data, &enrollments); err != nil {
+				return err
+			}
+		}
+		enrollments = append(enrollments, t.Captain)
+		if data, err = json.Marshal(enrollments); err != nil {
+			return err
+		} else {
+			if err = userDB.Put(b, data, nil); err != nil {
+				return err
+			}
+		}
+	}
+
+	var tournaments = make(map[string]*teamPayload)
+	if data, err := userDB.Get([]byte(t.Captain), nil); err != nil && !isNotFound(err) {
+		return err
+	} else {
+		if data != nil {
+			if err = json.Unmarshal(data, &tournaments); err != nil {
+				return err
+			}
+		}
+		tournaments[string(b)] = t
+		if data, err = json.Marshal(tournaments); err != nil {
+			return err
+		} else {
+			if err = userDB.Put([]byte(t.Captain), data, nil); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func existsTeam(t uuid.UUID, captain string) (bool, error) {
+	if teams, err := getTournamentTeam(t); err != nil {
+		if isNotFound(err) {
+			return false, nil
+		}
+		return false, err
+	} else {
+		for _, team := range teams {
+			if team == captain {
+				return true, nil
+			}
+		}
+		return false, nil
+	}
+}
+
+func getTeam(t uuid.UUID, captain string) (*teamPayload, error) {
+	var b []byte
+	if p, err := getBytesTournament(t); err != nil {
+		return nil, err
+	} else {
+		b = p
+	}
+
+	var payload = make(map[string]*teamPayload)
+	if data, err := userDB.Get([]byte(captain), nil); err != nil && !isNotFound(err) {
+		return nil, err
+	} else {
+		if data != nil {
+			if err = json.Unmarshal(data, &payload); err != nil {
+				return nil, err
+			}
+		}
+		if p, ok := payload[string(b)]; !ok {
+			return nil, leveldb.ErrNotFound
+		} else {
+			return p, nil
+		}
+	}
+}
+
+func getTournamentTeam(t uuid.UUID) ([]string, error) {
+	var b []byte
+	if p, err := getBytesTournament(t); err != nil {
+		return nil, err
+	} else {
+		b = p
+	}
+
+	var payload []string
+	if data, err := userDB.Get(b, nil); err != nil && !isNotFound(err) {
+		return nil, err
+	} else {
+		if data != nil {
+			if err = json.Unmarshal(data, &payload); err != nil {
+				return nil, err
+			}
+		}
+		return payload, nil
+	}
+}
 
 func getAuthButton(user *discordgo.User) (authText, authRef string) {
 	authText = "Login"
 	authRef = "/auth/login"
 	if user != nil {
-		authText = user.Username + " (Click to logout)"
+		authText = user.Username
 		authRef = "/auth/logout"
 	}
 	return
 }
+
+func validateEnrollment(payload *userEnrollment) bool {
+	return len(payload.TeamName) <= 64 &&
+		len(payload.FirstName) > 0 && len(payload.FirstName) <= 64 &&
+		len(payload.LastName) > 0 && len(payload.LastName) <= 64 &&
+		len(payload.Player0) <= 64 &&
+		len(payload.Player1) <= 64 &&
+		len(payload.Player2) <= 64 &&
+		len(payload.Player3) <= 64
+}
+
+func bindEnrollment(context *gin.Context, user *discordgo.User, tsn int) *teamPayload {
+	payload := userEnrollment{}
+	if err := context.Bind(&payload); err != nil {
+		if config.System.Verbose {
+			log.Printf("error binding enrollment form: %s", err)
+		}
+		context.String(http.StatusBadRequest, "Bad Request")
+		return nil
+	}
+
+	if !validateEnrollment(&payload) {
+		if config.System.Verbose {
+			log.Print("invalid data submitted to enrollment form")
+			log.Print(payload)
+		}
+		context.String(http.StatusBadRequest, "Bad Request")
+		return nil
+	}
+
+	var players []string
+	if tsn > 0 {
+		if len(payload.TeamName) < 0 {
+			context.String(http.StatusBadRequest, "Bad Request")
+			return nil
+		}
+		players = make([]string, tsn)
+
+		if len(payload.Player0) > 0 {
+			players[0] = payload.Player0
+		} else {
+			context.String(http.StatusBadRequest, "Bad Request")
+			return nil
+		}
+	}
+	if tsn > 1 {
+		if len(payload.Player1) > 0 {
+			players[1] = payload.Player1
+		} else {
+			context.String(http.StatusBadRequest, "Bad Request")
+			return nil
+		}
+	}
+	if tsn > 2 {
+		if len(payload.Player2) > 0 {
+			players[2] = payload.Player2
+		} else {
+			context.String(http.StatusBadRequest, "Bad Request")
+			return nil
+		}
+	}
+	if tsn > 3 {
+		if len(payload.Player3) > 0 {
+			players[3] = payload.Player3
+		} else {
+			context.String(http.StatusBadRequest, "Bad Request")
+			return nil
+		}
+	}
+
+	return &teamPayload{
+		Name:               payload.TeamName,
+		SubmitterFirstName: payload.FirstName,
+		SubmitterLastName:  payload.LastName,
+		Captain:            user.ID,
+		Players:            players,
+	}
+}