package signup import ( "fmt" "log/slog" "net/http" "github.com/go-chi/chi/v5" "github.com/posthog/posthog-go" "tangled.sh/tangled.sh/core/appview/config" "tangled.sh/tangled.sh/core/appview/db" "tangled.sh/tangled.sh/core/appview/dns" "tangled.sh/tangled.sh/core/appview/email" "tangled.sh/tangled.sh/core/appview/pages" "tangled.sh/tangled.sh/core/appview/state/userutil" "tangled.sh/tangled.sh/core/appview/xrpcclient" "tangled.sh/tangled.sh/core/idresolver" ) type Signup struct { config *config.Config db *db.DB cf *dns.Cloudflare posthog posthog.Client xrpc *xrpcclient.Client idResolver *idresolver.Resolver pages *pages.Pages l *slog.Logger } func New(cfg *config.Config, database *db.DB, pc posthog.Client, idResolver *idresolver.Resolver, pages *pages.Pages, l *slog.Logger) *Signup { var cf *dns.Cloudflare if cfg.Cloudflare.ApiToken != "" && cfg.Cloudflare.ZoneId != "" { var err error cf, err = dns.NewCloudflare(cfg) if err != nil { l.Warn("failed to create cloudflare client, signup will be disabled", "error", err) } } return &Signup{ config: cfg, db: database, posthog: pc, idResolver: idResolver, cf: cf, pages: pages, l: l, } } func (s *Signup) Router() http.Handler { r := chi.NewRouter() r.Post("/", s.signup) r.Get("/complete", s.complete) r.Post("/complete", s.complete) return r } func (s *Signup) signup(w http.ResponseWriter, r *http.Request) { if s.cf == nil { http.Error(w, "signup is disabled", http.StatusFailedDependency) } emailId := r.FormValue("email") if !email.IsValidEmail(emailId) { s.pages.Notice(w, "login-msg", "Invalid email address.") return } exists, err := db.CheckEmailExistsAtAll(s.db, emailId) if err != nil { s.l.Error("failed to check email existence", "error", err) s.pages.Notice(w, "login-msg", "Failed to complete signup. Try again later.") return } if exists { s.pages.Notice(w, "login-msg", "Email already exists.") return } code, err := s.inviteCodeRequest() if err != nil { s.l.Error("failed to create invite code", "error", err) s.pages.Notice(w, "login-msg", "Failed to create invite code.") return } em := email.Email{ APIKey: s.config.Resend.ApiKey, From: s.config.Resend.SentFrom, To: emailId, Subject: "Verify your Tangled account", Text: `Copy and paste this code below to verify your account on Tangled. ` + code, Html: `
Copy and paste this code below to verify your account on Tangled.
` + code + `
%s.tngl.sh.`, username))
go func() {
err := db.DeleteInflightSignup(s.db, email)
if err != nil {
s.l.Error("failed to delete inflight signup", "error", err)
}
}()
return
}
}