Skip to content

Commit

Permalink
feat: auth, code snippet version, security
Browse files Browse the repository at this point in the history
  • Loading branch information
abinba committed Apr 26, 2024
1 parent 026fdd1 commit 3cb7d6f
Show file tree
Hide file tree
Showing 26 changed files with 560 additions and 131 deletions.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# CHANGELOG

## Version 0.0.1

#### NOTE: the code snippet schema changed.

For creating the code snippet, there should be two requests:

1) POST /api/v1/code_snippet -> Title, UserID (optional)
2) POST /api/v1/code_snippet_version -> CodeSnippetID, ProgramLanguageID, Text

This is done for being able to have versioning for the code snippets.

Features List:

- Code Snippets: Added GET /api/v1/user_code_snippets to retrieve user code snippets
- Code Snippets: Added POST /api/v1/review_comment to create reviews for code snippets
- Code Snippets: Added POST /api/v1/code_snippet_version to create a new version of the code snippet
- Authorization: Added POST /api/v1/register and POST /api/v1/login
- Authorization: Added JWT token generation and check for login / signup
- Authorization: Added validation of username and password
- Security: Added security middleware
- Database: Programming languages are inserted with the first migration
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=codereview
JWT_SECRET_KEY=secret
```

And ensure that you have `db.env` file with the following contents (input your own password):
Expand Down Expand Up @@ -61,7 +62,7 @@ atlas migrate diff --env gorm
To apply the migration, use:

```
atlas migrate apply -u "postgres://postgres:postgres@localhost:5432/codereview"
atlas migrate apply -u "postgres://postgres:postgres@localhost:5432/codereview?sslmode=disable"
```

To downgrade:
Expand Down
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/alecthomas/kong v0.9.0 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
Expand All @@ -43,9 +45,9 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA
github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -46,6 +48,7 @@ github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtg
github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc=
github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
Expand Down Expand Up @@ -145,6 +148,8 @@ golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
Expand Down Expand Up @@ -180,6 +185,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand Down
38 changes: 17 additions & 21 deletions handler/code_snippet_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
)

type CodeSnippetForm struct {
ProgramLanguageID uuid.UUID `json:"program_language_id" description:"The UUID of the program language"`
Text string `json:"text" description:"The text of the code snippet, its content"`
IsPrivate bool `json:"is_private" description:"Whether the code snippet is private"`
IsArchived bool `json:"is_archived" description:"Whether the code snippet is archived"`
IsDraft bool `json:"is_draft" description:"Whether the code snippet is a draft"`
UserID uuid.UUID `json:"user_id" description:"UUID of the user"`
Title string `json:"title" description:"The title of the code snippet"`
IsPrivate bool `json:"is_private" description:"Whether the code snippet is private"`
IsArchived bool `json:"is_archived" description:"Whether the code snippet is archived"`
IsDraft bool `json:"is_draft" description:"Whether the code snippet is a draft"`
}

type ProgramLanguageForm struct {
Expand All @@ -35,7 +35,12 @@ func GetAllCodeSnippets(c *fiber.Ctx) error {
db := database.DB.Db

var code_snippets []model.CodeSnippet
db.Find(&code_snippets).Order("created_at desc")
// TODO: Select only needed fields, exclude user.password :)
db.Preload("CodeSnippetVersions").
Preload("User").
Preload("CodeSnippetVersions.ProgramLanguage").
Order("created_at desc").
Find(&code_snippets)

if len(code_snippets) == 0 {
return c.Status(404).JSON(fiber.Map{
Expand All @@ -62,7 +67,12 @@ func GetSingleCodeSnippet(c *fiber.Ctx) error {
id := c.Params("id")

var code_snippet model.CodeSnippet
db.Where("code_snippet_id = ?", id).First(&code_snippet)
// TODO: Select only needed fields, exclude user.password :)
db.Preload("CodeSnippetVersions").
Preload("User").
Preload("CodeSnippetVersions.ProgramLanguage").
Preload("CodeSnippetVersions.ReviewComments").
Preload("CodeSnippetVersions.CodeSnippetRatings").Where("code_snippet_id = ?", id).First(&code_snippet)

if code_snippet.CodeSnippetID == uuid.Nil {
return c.Status(404).JSON(fiber.Map{
Expand All @@ -85,27 +95,13 @@ func GetSingleCodeSnippet(c *fiber.Ctx) error {
func CreateCodeSnippet(c *fiber.Ctx) error {
db := database.DB.Db

user := new(model.User)
user.Username = GenerateRandomUsername(8)
if result := db.Create(&user); result.Error != nil {
return c.Status(500).JSON(fiber.Map{
"status": "error", "message": "Could not create anonymous user", "data": result.Error,
})
}

code_snippet := new(model.CodeSnippet)
if err := c.BodyParser(code_snippet); err != nil {
return c.Status(400).JSON(fiber.Map{
"status": "error", "message": "Could not parse request", "data": nil,
})
}

program_language := new(model.ProgramLanguage)
db.First(&program_language, code_snippet.ProgramLanguageID)

code_snippet.UserID = user.UserID
code_snippet.User = *user
code_snippet.ProgramLanguage = *program_language
if result := db.Create(&code_snippet); result.Error != nil {
return c.Status(500).JSON(fiber.Map{
"status": "error", "message": "Could not create code snippet", "data": result.Error,
Expand Down
37 changes: 37 additions & 0 deletions handler/code_snippet_version_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package handler

import (
"github.com/abinba/codereview/database"
"github.com/abinba/codereview/model"
"github.com/gofiber/fiber/v2"
)

// CreateCodeSnippet godoc
// @Summary Create a code snippet
// @Description Create a code snippet
// @Tags Code Snippets
// @Accept json
// @Produce json
// @Param code_snippet body CodeSnippetVersion true "Code Snippet information to create"
// @Success 201 {object} model.CodeSnippet
// @Router /api/v1/code_snippet/ [post]
func CreateCodeSnippetVersion(c *fiber.Ctx) error {
db := database.DB.Db

code_snippet := new(model.CodeSnippetVersion)
if err := c.BodyParser(code_snippet); err != nil {
return c.Status(400).JSON(fiber.Map{
"status": "error", "message": "Could not parse request", "data": err,
})
}

if result := db.Create(&code_snippet); result.Error != nil {
return c.Status(500).JSON(fiber.Map{
"status": "error", "message": "Could not create code snippet version", "data": result.Error,
})
}

return c.Status(201).JSON(fiber.Map{
"status": "success", "message": "Code Snippet Version has been created", "data": code_snippet,
})
}
37 changes: 37 additions & 0 deletions handler/review_comment_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package handler

import (
"github.com/abinba/codereview/database"
"github.com/abinba/codereview/model"
"github.com/gofiber/fiber/v2"
)

// CreateReviewComment creates a review comment.
// @Summary Create a review comment
// @Description Adds a new review comment to the database.
// @Tags Review Comments
// @Accept json
// @Produce json
// @Param review_comment body ReviewComment true "Review comment information to create"
// @Success 201 {object} model.ReviewComment
// @Router /api/v1/review_comment/ [post]
func CreateReviewComment(c *fiber.Ctx) error {
db := database.DB.Db

review_comment := new(model.ReviewComment)
if err := c.BodyParser(review_comment); err != nil {
return c.Status(400).JSON(fiber.Map{
"status": "error", "message": "Could not parse request", "data": err,
})
}

if result := db.Create(&review_comment); result.Error != nil {
return c.Status(500).JSON(fiber.Map{
"status": "error", "message": "Could not create review comment", "data": result.Error,
})
}

return c.Status(201).JSON(fiber.Map{
"status": "success", "message": "Review comment has been created", "data": review_comment,
})
}
42 changes: 42 additions & 0 deletions handler/user_code_snippets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package handler

import (
"github.com/abinba/codereview/database"
"github.com/abinba/codereview/model"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
)

// GetUserCodeSnippets godoc
// @Summary Get user's code snippets
// @Description Retrieve all code snippets created by a specific user
// @Tags User Code Snippets
// @Accept json
// @Produce json
// @Param user_id path string true "User ID"
// @Success 200 {array} model.CodeSnippet
// @Router /api/v1/user_code_snippets/{user_id} [get]
func GetUserCodeSnippets(c *fiber.Ctx) error {
db := database.DB.Db
userID := c.Params("id")

var userUUID uuid.UUID
if err := userUUID.UnmarshalText([]byte(userID)); err != nil {
return c.Status(400).JSON(fiber.Map{
"status": "error", "message": "Invalid user ID format", "data": nil,
})
}

var code_snippets []model.CodeSnippet
db.Where("user_id = ?", userUUID).Preload("CodeSnippetVersions").Preload("User").Find(&code_snippets)

if len(code_snippets) == 0 {
return c.Status(404).JSON(fiber.Map{
"status": "error", "message": "No code snippets found for the user", "data": nil,
})
}

return c.Status(200).JSON(fiber.Map{
"status": "success", "message": "User code snippets found", "data": code_snippets,
})
}
Loading

0 comments on commit 3cb7d6f

Please sign in to comment.