package forge import ( "errors" "html/template" "net/http" "os" "path/filepath" "strings" ) const renameSchema = ` create table if not exists renames ( old text primary key, user integer not null references users(id) on delete cascade ); ` func (s *Store) RenameUser(id int64, old, name string) error { if err := checkName(name); err != nil { return err } tx, err := s.db.Begin() if err != nil { return err } defer tx.Rollback() if _, err := tx.Exec(`update users set name = ? where id = ?`, name, id); err != nil { if strings.Contains(err.Error(), "UNIQUE") { return errors.New("That name is taken") } return err } tx.Exec(`delete from renames where old = ?`, name) if _, err := tx.Exec(`insert into renames(old, user) values (?, ?) on conflict(old) do update set user = excluded.user`, old, id); err != nil { return err } return tx.Commit() } func (s *Store) Renamed(old string) (User, bool) { var id int64 if s.db.QueryRow(`select r.user from renames r where r.old = ? and not exists (select 1 from users where name = r.old)`, old).Scan(&id) != nil { return User{}, false } u, err := s.userByID(id) return u, err == nil } func (s *Server) rename(w http.ResponseWriter, r *http.Request) { u := s.require(w, r) if u == nil { return } fail := func(msg string) { http.Redirect(w, r, "/settings?error="+template.URLQueryEscaper(msg)+"#username", http.StatusSeeOther) } name := strings.ToLower(strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(r.FormValue("name")), "~"))) if name == u.Name { http.Redirect(w, r, "/settings", http.StatusSeeOther) return } if _, ok := s.store.CheckPassword(u.Name, r.FormValue("password")); !ok { s.limiter.fail(clientIP(r)) fail("Password is wrong") return } if err := s.store.RenameUser(u.ID, u.Name, name); err != nil { fail(err.Error()) return } from, to := filepath.Join(s.root, "repos", u.Name), filepath.Join(s.root, "repos", name) os.Remove(to) if err := os.Rename(from, to); err != nil && !os.IsNotExist(err) { s.store.RenameUser(u.ID, name, u.Name) fail("Could not move your repositories") return } http.Redirect(w, r, "/settings", http.StatusSeeOther) }