updating rest-api
This commit is contained in:
2
rest-api/api-test/cancel-registration.http
Normal file
2
rest-api/api-test/cancel-registration.http
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
DELETE http://localhost:8080/events/1/register
|
||||||
|
authorization:
|
||||||
7
rest-api/api-test/login.http
Normal file
7
rest-api/api-test/login.http
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
POST http://localhost:8080/login
|
||||||
|
content-type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"email": "test3@example.com",
|
||||||
|
"password": "something"
|
||||||
|
}
|
||||||
3
rest-api/api-test/register.http
Normal file
3
rest-api/api-test/register.http
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
POST http://localhost:8080/events/1/register
|
||||||
|
authorization:
|
||||||
|
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
26
rest-api/middlewares/auth.go
Normal file
26
rest-api/middlewares/auth.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
51
rest-api/routes/register.go
Normal file
51
rest-api/routes/register.go
Normal 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!"})
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
53
rest-api/utils/jwt.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user