updating rest-api

This commit is contained in:
2026-07-17 11:56:13 -05:00
parent 39433a0044
commit c4d7f52d37
15 changed files with 276 additions and 9 deletions

View File

@@ -0,0 +1,2 @@
DELETE http://localhost:8080/events/1/register
authorization:

View File

@@ -0,0 +1,7 @@
POST http://localhost:8080/login
content-type: application/json
{
"email": "test3@example.com",
"password": "something"
}

View File

@@ -0,0 +1,3 @@
POST http://localhost:8080/events/1/register
authorization:

View File

@@ -54,4 +54,20 @@ func createTables() {
if err != nil { if err != nil {
panic("Could not create table") panic("Could not create table")
} }
createRegistrationsTable := `
CREATE TABLE IF NOT EXISTS registrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_id INTEGER,
user_id INTEGER,
FOREIGN KEY(event_id) REFERENCES events(id),
FOREIGN KEY(user_id) REFERENCES users(id)
)
`
_, err = DB.Exec(createRegistrationsTable)
if err != nil {
panic("Could not create table")
}
} }

View File

@@ -15,6 +15,7 @@ require (
github.com/go-playground/validator/v10 v10.30.3 // indirect github.com/go-playground/validator/v10 v10.30.3 // indirect
github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect

View File

@@ -24,6 +24,8 @@ github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=

View File

@@ -0,0 +1,26 @@
package middlewares
import (
"net/http"
"example.com/rest-api/utils"
"github.com/gin-gonic/gin"
)
func Authenticate(context *gin.Context) {
token := context.Request.Header.Get("Authorization")
if token == "" {
context.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Not authorized"})
return
}
userId, err := utils.VerifyToken(token)
if err != nil {
context.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Not authorized"})
}
context.Set("userId", userId)
context.Next()
}

View File

@@ -12,12 +12,12 @@ type Event struct {
Description string `binding:"required"` Description string `binding:"required"`
Location string `binding:"required"` Location string `binding:"required"`
DateTime time.Time `binding:"required"` DateTime time.Time `binding:"required"`
UserID int UserID int64
} }
var events []Event = []Event{} var events []Event = []Event{}
func (e Event) Save() error { func (e *Event) Save() error {
query := ` query := `
INSERT INTO events(name, description, location, dateTime, user_id) INSERT INTO events(name, description, location, dateTime, user_id)
VALUES (?, ?, ?, ?, ?)` VALUES (?, ?, ?, ?, ?)`
@@ -105,3 +105,33 @@ func (event Event) Delete() error {
return err return err
} }
func (e Event) Register(userId int64) error {
query := "INSERT INTO registrations(event_id, user_id) VALUES (?, ?)"
stmt, err := db.DB.Prepare(query)
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(e.ID, userId)
return err
}
func (e Event) CancelRegistration(userId int64) error {
query := "DELETE FROM registrations WHERE event_id = ? AND user_id = ?"
stmt, err := db.DB.Prepare(query)
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(e.ID, userId)
return err
}

View File

@@ -1,6 +1,8 @@
package models package models
import ( import (
"errors"
"example.com/rest-api/db" "example.com/rest-api/db"
"example.com/rest-api/utils" "example.com/rest-api/utils"
) )
@@ -38,3 +40,23 @@ func (u User) Save() error {
u.ID = userId u.ID = userId
return err return err
} }
func (u *User) ValidateCredentials() error {
query := "SELECT id, password FROM users WHERE email = ?"
row := db.DB.QueryRow(query, u.Email)
var retrievedPassword string
err := row.Scan(&u.ID, &retrievedPassword)
if err != nil {
return errors.New("Credentials invalid")
}
passwordIsValid := utils.CheckPasswordHash(u.Password, retrievedPassword)
if !passwordIsValid {
return errors.New("Credentials invalid")
}
return nil
}

View File

@@ -42,8 +42,8 @@ func createEvent(context *gin.Context) {
return return
} }
event.ID = 1 userId := context.GetInt64("userId")
event.UserID = 1 event.UserID = userId
err = event.Save() err = event.Save()
@@ -61,13 +61,19 @@ func updateEvent(context *gin.Context) {
return return
} }
_, err = models.GetEventByID(eventId) userId := context.GetInt64("userId")
event, err := models.GetEventByID(eventId)
if err != nil { if err != nil {
context.JSON(http.StatusInternalServerError, gin.H{"message": "Could not fetch the event."}) context.JSON(http.StatusInternalServerError, gin.H{"message": "Could not fetch the event."})
return return
} }
if event.UserID != userId {
context.JSON(http.StatusUnauthorized, gin.H{"message": "Not authorized to update event"})
return
}
var updatedEvent models.Event var updatedEvent models.Event
err = context.ShouldBindJSON(&updatedEvent) err = context.ShouldBindJSON(&updatedEvent)
@@ -92,6 +98,7 @@ func deleteEvent(context *gin.Context) {
return return
} }
userId := context.GetInt64("userId")
event, err := models.GetEventByID(eventId) event, err := models.GetEventByID(eventId)
if err != nil { if err != nil {
@@ -99,6 +106,11 @@ func deleteEvent(context *gin.Context) {
return return
} }
if event.UserID != userId {
context.JSON(http.StatusUnauthorized, gin.H{"message": "Not authorized to delete event"})
return
}
err = event.Delete() err = event.Delete()
if err != nil { if err != nil {

View File

@@ -0,0 +1,51 @@
package routes
import (
"net/http"
"strconv"
"example.com/rest-api/models"
"github.com/gin-gonic/gin"
)
func registerForEvent(context *gin.Context) {
userId := context.GetInt64("userId")
eventId, err := strconv.ParseInt(context.Param("id"), 10, 64)
if err != nil {
context.JSON(http.StatusBadRequest, gin.H{"message": "Could not parse event id."})
return
}
event, err := models.GetEventByID(eventId)
if err != nil {
context.JSON(http.StatusInternalServerError, gin.H{"message": "Could not fetch event."})
return
}
err = event.Register(userId)
if err != nil {
context.JSON(http.StatusInternalServerError, gin.H{"message": "Could not register user for event"})
return
}
context.JSON(http.StatusCreated, gin.H{"message": "Registered!"})
}
func cancelRegistration(context *gin.Context) {
userId := context.GetInt64("userId")
eventId, err := strconv.ParseInt(context.Param("id"), 10, 64)
var event models.Event
event.ID = eventId
err = event.CancelRegistration(userId)
if err != nil {
context.JSON(http.StatusInternalServerError, gin.H{"message": "Could not cancel registration"})
return
}
context.JSON(http.StatusOK, gin.H{"message": "Cancelled!"})
}

View File

@@ -1,12 +1,22 @@
package routes package routes
import "github.com/gin-gonic/gin" import (
"example.com/rest-api/middlewares"
"github.com/gin-gonic/gin"
)
func RegisterRoutes(server *gin.Engine) { func RegisterRoutes(server *gin.Engine) {
server.GET("/events", getEvents) server.GET("/events", getEvents)
server.GET("/events/:id", getEvent) server.GET("/events/:id", getEvent)
server.POST("/events", createEvent)
server.PUT("/events/:id", updateEvent) authenticated := server.Group("/")
server.DELETE("/events/:id", deleteEvent) authenticated.Use(middlewares.Authenticate)
authenticated.POST("/events", createEvent)
authenticated.PUT("/events/:id", updateEvent)
authenticated.DELETE("/events/:id", deleteEvent)
authenticated.POST("/events/:id/register", registerForEvent)
authenticated.DELETE("/events/:id/register", cancelRegistration)
server.POST("/signup", signup) server.POST("/signup", signup)
server.POST("/login", login)
} }

View File

@@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"example.com/rest-api/models" "example.com/rest-api/models"
"example.com/rest-api/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -26,3 +27,29 @@ func signup(context *gin.Context) {
context.JSON(http.StatusCreated, gin.H{"message": "User created successfully"}) context.JSON(http.StatusCreated, gin.H{"message": "User created successfully"})
} }
func login(context *gin.Context) {
var user models.User
err := context.ShouldBindJSON(&user)
if err != nil {
context.JSON(http.StatusBadRequest, gin.H{"message": "Could not parse request data."})
return
}
err = user.ValidateCredentials()
if err != nil {
context.JSON(http.StatusUnauthorized, gin.H{"message": "Could not authenticate user."})
}
token, err := utils.GenerateToken(user.Email, user.ID)
if err != nil {
context.JSON(http.StatusInternalServerError, gin.H{"message": "Could not authenticate user."})
return
}
context.JSON(http.StatusOK, gin.H{"message": "Login successful!", "token": token})
}

View File

@@ -6,3 +6,8 @@ func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err return string(bytes), err
} }
func CheckPasswordHash(password, hashedPassword string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}

53
rest-api/utils/jwt.go Normal file
View File

@@ -0,0 +1,53 @@
package utils
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
const secretKey = "supersecret"
func GenerateToken(email string, userId int64) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"email": "",
"userId": "",
"exp": time.Now().Add(time.Hour * 2).Unix(),
})
return token.SignedString([]byte(secretKey))
}
func VerifyToken(token string) (int64, error) {
parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
_, ok := token.Method.(*jwt.SigningMethodHMAC)
if !ok {
return nil, errors.New("Unexpected signing method")
}
return secretKey, nil
})
if err != nil {
return 0, errors.New("Could not parse token.")
}
tokenIsValid := parsedToken.Valid
if !tokenIsValid {
return 0, errors.New("Invalid token")
}
claims, ok := parsedToken.Claims.(jwt.MapClaims)
if !ok {
return 0, errors.New("Invalid token claims.")
}
// email := claims["email"].(string)
userId := int64(claims["userId"].(float64))
return userId, nil
}