Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

亻尔女子 #4

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
.idea
.idea
l1nk4i/config.toml
.gitignore
l1nk4i/test
20 changes: 20 additions & 0 deletions l1nk4i/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM golang:1.23.1 AS builder

WORKDIR /app

COPY . .

RUN go build -o myapp
Comment on lines +1 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct Go version and optimize build process

There are several issues in the builder stage that need to be addressed:

  1. The Go version 1.23.1 does not exist. Use a valid and stable version, such as 1.21.x.
  2. Copying the entire context may include unnecessary files. Consider using a .dockerignore file to exclude non-essential files.
  3. The build command can be optimized for smaller binary size and better performance.

Here's a suggested improvement:

-FROM golang:1.23.1 AS builder
+FROM golang:1.21 AS builder

WORKDIR /app

-COPY . .
+COPY go.mod go.sum ./
+RUN go mod download
+COPY . .

-RUN go build -o myapp
+RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o myapp .

Also, create a .dockerignore file in your project root with the following content:

.git
.gitignore
README.md
Dockerfile

This will help reduce the build context size and speed up the build process.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM golang:1.23.1 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o myapp .


FROM ubuntu:latest

WORKDIR /app

Comment on lines +9 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use a smaller base image for the final stage

Using ubuntu:latest as the base image for a Go application results in an unnecessarily large container. Since Go binaries are statically linked by default, you can use a much smaller base image.

Consider using alpine or even scratch as the base image for the final stage:

-FROM ubuntu:latest
+FROM alpine:latest
# or
+FROM scratch

WORKDIR /app

If you choose alpine, you might need to install CA certificates:

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /app

Using a smaller base image will significantly reduce the final image size and potential attack surface.

COPY --from=builder /app/myapp /app/
COPY config.toml .
COPY entrypoint.sh .
RUN chmod +x entrypoint.sh
Comment on lines +13 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Missing config.toml in the repository

The verification script indicates that config.toml is missing from the repository. This will cause the Docker build to fail when attempting to copy config.toml.

  • Ensure that config.toml is present in the repository at the expected location.
  • If config.toml is generated during the build process, update the Dockerfile to reflect its creation before the COPY command.
🔗 Analysis chain

LGTM! Consider adding a verification step

The process of copying files and setting permissions looks good. However, to ensure the build doesn't fail due to missing files, consider adding a verification step.

You can add a simple check in your CI/CD pipeline or as a pre-build step to verify the existence of required files:

This will help catch any issues early in the build process.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify the existence of required files
if [ ! -f config.toml ] || [ ! -f entrypoint.sh ]; then
    echo "Error: config.toml or entrypoint.sh is missing"
    exit 1
fi

Length of output: 140


Script:

#!/bin/bash
# Search for config.toml in the repository
fd 'config.toml' || echo "config.toml not found in the repository."

# Search for entrypoint.sh in the repository
fd 'entrypoint.sh' || echo "entrypoint.sh not found in the repository."

Length of output: 56


ENTRYPOINT ["./entrypoint.sh"]

EXPOSE 8080
32 changes: 32 additions & 0 deletions l1nk4i/api/admin/deleteAnswer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package admin

import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"l1nk4i/db"
"net/http"
)

func DeleteAnswer(c *gin.Context) {
answerID := c.Param("answer-id")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add input validation for the answer ID.

While the deletion logic is correctly delegated to the db package, there's no validation of the answerID format before using it. This could lead to unnecessary database calls or potential security issues if the ID is malformed.

Consider adding validation for the answerID. For example:

answerID := c.Param("answer-id")
if !isValidAnswerID(answerID) {
    c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer-id format"})
    return
}

// ... rest of the function

func isValidAnswerID(id string) bool {
    // Implement validation logic here
    // For example, check if it's a valid UUID or matches expected format
    return true // Placeholder
}

Also applies to: 25-29


session := sessions.Default(c)
role, exists := session.Get("role").(string)
if !exists {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"})
return
}

if role != "admin" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}

err := db.DeleteAnswer(answerID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer-id"})
return
}

c.JSON(http.StatusOK, gin.H{"msg": "delete answer successful!"})
}
32 changes: 32 additions & 0 deletions l1nk4i/api/admin/deleteQuestion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package admin

import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"l1nk4i/db"
"net/http"
)

func DeleteQuestion(c *gin.Context) {
questionID := c.Param("question-id")

session := sessions.Default(c)
role, exists := session.Get("role").(string)
if !exists {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"})
return
}

if role != "admin" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}

err := db.DeleteQuestion(questionID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question-id"})
return
}
Comment on lines +25 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Improve error handling and add logging.

While the deletion logic is correct, the error handling can be improved. Not all errors from db.DeleteQuestion may be due to an invalid question-id. Consider logging the error and providing a more generic error message to the client.

Here's a suggested improvement:

 	err := db.DeleteQuestion(questionID)
 	if err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question-id"})
+		log.Printf("Error deleting question %s: %v", questionID, err)
+		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete question"})
 		return
 	}

Also, consider adding a package-level logger or using a logging middleware for consistent logging across the application.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
err := db.DeleteQuestion(questionID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question-id"})
return
}
err := db.DeleteQuestion(questionID)
if err != nil {
log.Printf("Error deleting question %s: %v", questionID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete question"})
return
}


c.JSON(http.StatusOK, gin.H{"msg": "delete question successful!"})
}
50 changes: 50 additions & 0 deletions l1nk4i/api/answer/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package answer

import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"l1nk4i/db"
"net/http"
)

func Create(c *gin.Context) {
questionID := c.Param("question-id")

var answerInfo struct {
Content string `json:"content"`
}

if err := c.ShouldBind(&answerInfo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"})
return
}

session := sessions.Default(c)
userID, exists := session.Get("user_id").(string)
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
return
}

// Verify that the question exists
_, err := db.GetQuestionByQuestionID(questionID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"})
return
}

answer := db.Answer{
AnswerID: uuid.New().String(),
UserID: userID,
QuestionID: questionID,
Content: answerInfo.Content,
}
if err := db.CreateAnswer(&answer); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "create answer error"})
return
}

c.JSON(http.StatusOK, gin.H{"answer_id": answer.AnswerID})
return
}
40 changes: 40 additions & 0 deletions l1nk4i/api/answer/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package answer

import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"l1nk4i/db"
"net/http"
)

func Delete(c *gin.Context) {
answerID := c.Param("answer-id")

// Verify user identity
session := sessions.Default(c)
userid, exists := session.Get("user_id").(string)
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
return
}

answer, err := db.GetAnswerByAnswerID(answerID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer_id"})
return
}

if answer.UserID != userid {
c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
return
}

// Delete answer
err = db.DeleteAnswer(answerID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "delete answer error"})
return
}

c.JSON(http.StatusOK, gin.H{"msg": "delete answer successful!"})
}
20 changes: 20 additions & 0 deletions l1nk4i/api/answer/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package answer

import (
"github.com/gin-gonic/gin"
"l1nk4i/db"
"net/http"
)

// Get gets answers to the question by question_id
func Get(c *gin.Context) {
questionID := c.Param("question-id")

answers, err := db.GetAnswersByQuestionID(questionID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"})
return
}

c.JSON(http.StatusOK, gin.H{"data": answers})
}
49 changes: 49 additions & 0 deletions l1nk4i/api/answer/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package answer

import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"l1nk4i/db"
"net/http"
)

func Update(c *gin.Context) {
answerID := c.Param("answer-id")

var answerInfo struct {
Content string `json:"content"`
}

if err := c.ShouldBindJSON(&answerInfo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"})
return
}

// Verify user identity
session := sessions.Default(c)
userid, exists := session.Get("user_id").(string)
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
return
}
Comment on lines +22 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance session handling and error messaging.

The current implementation correctly checks for user authentication, but there are a couple of improvements that can be made:

  1. The error message for invalid sessions could be more specific.
  2. It's a good practice to use constants for session keys to avoid typos and improve maintainability.

Consider applying the following changes:

  1. Improve the error message:
 if !exists {
-    c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
+    c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated", "details": "Valid session required"})
     return
 }
  1. Define a constant for the session key:
const UserIDKey = "user_id"

Then use it in the code:

-userid, exists := session.Get("user_id").(string)
+userid, exists := session.Get(UserIDKey).(string)


answer, err := db.GetAnswerByAnswerID(answerID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer_id"})
return
}

if answer.UserID != userid {
c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
return
}

// Update answer
err = db.UpdateAnswer(answerID, answerInfo.Content)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer"})
return
}

c.JSON(http.StatusOK, gin.H{"msg": "update answer successful!"})
}
53 changes: 53 additions & 0 deletions l1nk4i/api/question/best.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package question

import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"l1nk4i/db"
"net/http"
)

func Best(c *gin.Context) {
answerID := c.Param("answer-id")
questionID := c.Param("question-id")

// Verify user identity
session := sessions.Default(c)
userid, exists := session.Get("user_id").(string)
if !exists {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session"})
return
}

question, err := db.GetQuestionByQuestionID(questionID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question-id"})
return
}

if question.UserID != userid {
c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
return
}

// Validate answer-id
answer, err := db.GetAnswerByAnswerID(answerID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer-id"})
return
}

if answer.QuestionID != questionID {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid answer-id"})
return
}

// Set the best answer
err = db.UpdateBestAnswer(questionID, answerID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "update best answer failed"})
return
}

c.JSON(http.StatusOK, gin.H{"msg": "update best answer successful!"})
}
42 changes: 42 additions & 0 deletions l1nk4i/api/question/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package question

import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"l1nk4i/db"
"net/http"
)

func Create(c *gin.Context) {
var questionInfo struct {
Title string `json:"title"`
Content string `json:"content"`
}

if err := c.ShouldBind(&questionInfo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"})
return
}

session := sessions.Default(c)
userID, exists := session.Get("user_id").(string)
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
return
}
Comment on lines +22 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding validation for the user ID.

While the function correctly retrieves and checks for the existence of a user ID in the session, it doesn't validate the format or content of the user ID.

Consider adding a validation step for the user ID:

 	userID, exists := session.Get("user_id").(string)
 	if !exists {
 		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
 		return
 	}
+	if !isValidUserID(userID) {
+		c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid user ID"})
+		return
+	}

 // Add this helper function
+func isValidUserID(id string) bool {
+	// Implement your validation logic here
+	// For example, check if it's a valid UUID
+	_, err := uuid.Parse(id)
+	return err == nil
+}

Committable suggestion was skipped due to low confidence.


question := db.Question{
QuestionID: uuid.New().String(),
UserID: userID,
Title: questionInfo.Title,
Content: questionInfo.Content,
}
if err := db.CreateQuestion(&question); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "create question error"})
return
}
Comment on lines +29 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation and improve error handling for question creation.

While the function correctly creates a new question and interacts with the database, there are a few areas for improvement:

  1. Input validation for the question title and content is missing.
  2. The error message for database interaction could be more specific.

Consider implementing these improvements:

  1. Add input validation:
+	if len(questionInfo.Title) == 0 || len(questionInfo.Content) == 0 {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Title and content cannot be empty"})
+		return
+	}
+	// Add more validation as needed (e.g., max length)
  1. Improve error handling:
 	if err := db.CreateQuestion(&question); err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": "create question error"})
+		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create question: " + err.Error()})
 		return
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
question := db.Question{
QuestionID: uuid.New().String(),
UserID: userID,
Title: questionInfo.Title,
Content: questionInfo.Content,
}
if err := db.CreateQuestion(&question); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "create question error"})
return
}
if len(questionInfo.Title) == 0 || len(questionInfo.Content) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "Title and content cannot be empty"})
return
}
// Add more validation as needed (e.g., max length)
question := db.Question{
QuestionID: uuid.New().String(),
UserID: userID,
Title: questionInfo.Title,
Content: questionInfo.Content,
}
if err := db.CreateQuestion(&question); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create question: " + err.Error()})
return
}


c.JSON(http.StatusOK, gin.H{"question_id": question.QuestionID})
return
}
Comment on lines +40 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using HTTP 201 Created status for successful question creation.

While the current implementation returns a 200 OK status, it's more RESTful to use 201 Created when a new resource is successfully created.

Consider modifying the response as follows:

-	c.JSON(http.StatusOK, gin.H{"question_id": question.QuestionID})
+	c.JSON(http.StatusCreated, gin.H{"question_id": question.QuestionID})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
c.JSON(http.StatusOK, gin.H{"question_id": question.QuestionID})
return
}
c.JSON(http.StatusCreated, gin.H{"question_id": question.QuestionID})
return
}

40 changes: 40 additions & 0 deletions l1nk4i/api/question/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package question

import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"l1nk4i/db"
"net/http"
)

func Delete(c *gin.Context) {
questionID := c.Param("question-id")

// Verify user identity
session := sessions.Default(c)
userid, exists := session.Get("user_id").(string)
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
return
}
Comment on lines +15 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the session user_id retrieval logic

The current code uses a type assertion to retrieve user_id from the session, but it incorrectly interprets the result. The type assertion .(string) returns two values: the asserted value and a boolean indicating success. The variable exists actually indicates whether the type assertion succeeded, not whether the user_id exists in the session.

To correctly check for the existence of user_id and ensure it's of type string, consider the following modification:

-userid, exists := session.Get("user_id").(string)
-if !exists {
+userIDInterface := session.Get("user_id")
+if userIDInterface == nil {
     c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
     return
 }
+userid, ok := userIDInterface.(string)
+if !ok {
+    c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
+    return
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
userid, exists := session.Get("user_id").(string)
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
return
}
userIDInterface := session.Get("user_id")
if userIDInterface == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
return
}
userid, ok := userIDInterface.(string)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid session"})
return
}


question, err := db.GetQuestionByQuestionID(questionID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid question_id"})
return
}

if question.UserID != userid {
c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
return
}

// Delete question
err = db.DeleteQuestion(questionID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "delete question error"})
return
}

c.JSON(http.StatusOK, gin.H{"msg": "delete question successful!"})
}
Loading