This commit is contained in:
angelicatesvara07-crypto
2026-09-28 14:56:41 +07:00
parent f8214fda7d
commit 47f490f5a3
23 changed files with 881 additions and 72 deletions
+142
View File
@@ -0,0 +1,142 @@
package handler
import (
"encoding/json"
"net/http"
"strings"
"time"
"5803025015/w04/internal/auth"
"5803025015/w04/internal/models"
"5803025015/w04/internal/store"
"github.com/google/uuid"
)
type AuthHandler struct {
userStore *store.UserStore
sessionStore *store.SessionStore
}
func NewAuthHandler(userStore *store.UserStore, sessionStore *store.SessionStore) *AuthHandler {
return &AuthHandler{
userStore: userStore,
sessionStore: sessionStore,
}
}
type authRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
var req authRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
req.Username = strings.TrimSpace(req.Username)
if len(req.Username) < 3 || len(req.Username) > 32 || req.Password == "" {
http.Error(w, "Invalid username or password", http.StatusBadRequest)
return
}
hash, err := auth.HashPassword(req.Password)
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
user := &models.User{
ID: uuid.New().String(),
Username: req.Username,
PasswordHash: hash,
}
if err := h.userStore.Create(r.Context(), user); err != nil {
http.Error(w, "Username already exists or create failed", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(user)
}
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
var req authRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
user, err := h.userStore.GetByUsername(r.Context(), req.Username)
if err != nil || !auth.CheckPassword(req.Password, user.PasswordHash) {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
token, err := auth.GenerateToken()
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
session := &models.Session{
Token: token,
UserID: user.ID,
CreatedAt: time.Now(),
}
if err := h.sessionStore.Create(r.Context(), session); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(session)
}
func (h *AuthHandler) Logout(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
token := authHeader
if strings.HasPrefix(strings.ToLower(authHeader), "bearer ") {
token = authHeader[7:]
}
token = strings.TrimSpace(token)
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err := h.sessionStore.Delete(r.Context(), token); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(map[string]string{"message": "logged out"})
}
+165
View File
@@ -0,0 +1,165 @@
package handler
import (
"encoding/json"
"errors"
"net/http"
"strings"
"5803025015/w04/internal/middleware"
"5803025015/w04/internal/models"
"5803025015/w04/internal/store"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
type SnippetHandler struct {
snippetStore *store.SnippetStore
}
func NewSnippetHandler(snippetStore *store.SnippetStore) *SnippetHandler {
return &SnippetHandler{snippetStore: snippetStore}
}
type snippetRequest struct {
Title string `json:"title"`
Content string `json:"content"`
}
func (h *SnippetHandler) Snippets(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.List(w, r)
case http.MethodPost:
h.Create(w, r)
default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
}
func (h *SnippetHandler) SnippetByID(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.Get(w, r)
case http.MethodDelete:
h.Delete(w, r)
default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
}
func (h *SnippetHandler) Create(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
userID := middleware.GetUserID(r.Context())
if userID == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var req snippetRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
req.Title = strings.TrimSpace(req.Title)
req.Content = strings.TrimSpace(req.Content)
if req.Title == "" || req.Content == "" {
http.Error(w, "Title and content are required", http.StatusBadRequest)
return
}
snippet := &models.Snippet{
ID: uuid.New().String(),
Title: req.Title,
Content: req.Content,
UserID: userID,
}
if err := h.snippetStore.Create(r.Context(), snippet); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(snippet)
}
func (h *SnippetHandler) List(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
snippets, err := h.snippetStore.GetAll(r.Context())
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(snippets)
}
func (h *SnippetHandler) Get(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
id := r.PathValue("id")
if id == "" {
path := strings.TrimPrefix(r.URL.Path, "/snippets/")
id = strings.TrimSuffix(path, "/")
}
if id == "" {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
snippet, err := h.snippetStore.GetByID(r.Context(), id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
http.Error(w, "Snippet not found", http.StatusNotFound)
return
}
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(snippet)
}
func (h *SnippetHandler) Delete(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
id := r.PathValue("id")
if id == "" {
path := strings.TrimPrefix(r.URL.Path, "/snippets/")
id = strings.TrimSuffix(path, "/")
}
if id == "" {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
if err := h.snippetStore.Delete(r.Context(), id); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}