package main import ( "context" "database/sql" "encoding/json" "errors" "net/http" "strconv" "strings" "time" ) func healthHandler(w http.ResponseWriter, r *http.Request) { now := time.Now().Format(time.RFC3339) ok(w, &map[string]string{"status": "UP", "time": now}) } func studentsHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: listStudents(w, r) case http.MethodPost: createStudent(w, r) default: w.Header().Set("Allow", "GET, POST") fail(w, http.StatusMethodNotAllowed, "method not allowed") } } func studentByIDHandler(w http.ResponseWriter, r *http.Request) { idStr := strings.TrimPrefix(r.URL.Path, "/students/") id, err := strconv.ParseInt(idStr, 10, 64) if err != nil { fail(w, http.StatusBadRequest, "invalid student id") return } switch r.Method { case http.MethodGet: getStudent(w, r, id) case http.MethodPut: updateStudent(w, r, id) case http.MethodDelete: deleteStudent(w, r, id) default: w.Header().Set("Allow", "GET, PUT, DELETE") fail(w, http.StatusMethodNotAllowed, "method not allowed") } } func getStudent(w http.ResponseWriter, r *http.Request, id int64) { ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) defer cancel() var s Student err := db.QueryRowContext(ctx, `SELECT id,name,age,email,created_at,updated_at FROM students WHERE id=?`, id, ).Scan(&s.ID, &s.Name, &s.Age, &s.Email, &s.CreatedAt, &s.UpdatedAt) if errors.Is(err, sql.ErrNoRows) { fail(w, http.StatusNotFound, "student not found") return } else if err != nil { fail(w, http.StatusInternalServerError, "db query error: "+err.Error()) return } ok(w, &s) } func updateStudent(w http.ResponseWriter, r *http.Request, id int64) { if ct := r.Header.Get("Content-Type"); !strings.HasPrefix(strings.ToLower(ct), "application/json") { fail(w, http.StatusUnsupportedMediaType, "Content-Type must be application/json") return } var in struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&in); err != nil { fail(w, http.StatusBadRequest, "invalid json: "+err.Error()) return } ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) defer cancel() res, err := db.ExecContext(ctx, `UPDATE students SET name=?, age=?, email=? WHERE id=?`, in.Name, in.Age, in.Email, id) if err != nil { if strings.Contains(strings.ToLower(err.Error()), "duplicate") { fail(w, http.StatusConflict, "email already exists") return } fail(w, http.StatusInternalServerError, "update error: "+err.Error()) return } n, _ := res.RowsAffected() if n == 0 { fail(w, http.StatusNotFound, "student not found") return } getStudent(w, r, id) } func deleteStudent(w http.ResponseWriter, r *http.Request, id int64) { ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) defer cancel() res, err := db.ExecContext(ctx, `DELETE FROM students WHERE id=?`, id) if err != nil { fail(w, http.StatusInternalServerError, "delete error: "+err.Error()) return } n, _ := res.RowsAffected() if n == 0 { fail(w, http.StatusNotFound, "student not found") return } ok(w, &map[string]any{"deleted": id}) } func createStudent(w http.ResponseWriter, r *http.Request) { if ct := r.Header.Get("Content-Type"); !strings.HasPrefix(strings.ToLower(ct), "application/json") { fail(w, http.StatusUnsupportedMediaType, "Context-Type must be application/json") return } var in struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email"` } if err := json.NewDecoder(r.Body).Decode(&in); err != nil { fail(w, http.StatusBadRequest, "invalid json: "+err.Error()) return } if strings.TrimSpace(in.Name) == "" || in.Age <= 0 || strings.TrimSpace(in.Email) == "" { fail(w, http.StatusBadRequest, "name/age/email required") return } ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) defer cancel() res, err := db.ExecContext(ctx, `INSERT INTO students(name, age, email) VALUES(?,?,?)`, in.Name, in.Age, in.Email) if err != nil { fail(w, http.StatusInternalServerError, "insert error: "+err.Error()) return } id, _ := res.LastInsertId() var out Student err = db.QueryRowContext(ctx, `SELECT id,name,age,email,created_at,updated_at FROM students WHERE id=?`, id, ).Scan(&out.ID, &out.Name, &out.Age, &out.Email, &out.CreatedAt, &out.UpdatedAt) if err != nil { fail(w, http.StatusInternalServerError, "fetch created row error: "+err.Error()) return } created(w, "/students/"+strconv.FormatInt(out.ID, 10), &out) } func listStudents(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second) defer cancel() rows, err := db.QueryContext(ctx, `SELECT id,name,age,email,created_at,updated_at FROM students ORDER BY id`) if err != nil { fail(w, http.StatusInternalServerError, "db query error: "+err.Error()) return } defer rows.Close() var items []Student for rows.Next() { var s Student if err := rows.Scan(&s.ID, &s.Name, &s.Age, &s.Email, &s.CreatedAt, &s.UpdatedAt); err != nil { fail(w, http.StatusInternalServerError, "scan error: "+err.Error()) return } items = append(items, s) } if err := rows.Err(); err != nil { fail(w, http.StatusInternalServerError, "rows error: "+err.Error()) return } ok(w, &items) }