Skip to content
Merged
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
3 changes: 2 additions & 1 deletion api/middleware/auth/paseto/paseto.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ func PASETO(authOptional bool) func(*gin.Context) {
c.AbortWithStatus(http.StatusInternalServerError)
return
}

c.Set(CTX_WALLET_ADDRES, userFetch.WalletAddress)
c.Set(CTX_USER_ID, cc.UserId)
c.Set(CTX_USER_ID, userFetch.UserId)
c.Next()
}
}
Expand Down
66 changes: 66 additions & 0 deletions api/v1/report/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package report

import (
"fmt"
"net/http"
"time"

"github.com/NetSepio/gateway/api/middleware/auth/paseto"
"github.com/NetSepio/gateway/config/dbconfig"
"github.com/NetSepio/gateway/models"
"github.com/NetSepio/gateway/util/pkg/logwrapper"
"github.com/TheLazarusNetwork/go-helpers/httpo"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"gorm.io/gorm"
)

func postReport(c *gin.Context) {
var request ReportRequest
if err := c.BindJSON(&request); err != nil {
httpo.NewErrorResponse(http.StatusBadRequest, fmt.Sprintf("Invalid request body: %s", err)).SendD(c)
return
}

db := dbconfig.GetDb()
userId := c.GetString(paseto.CTX_USER_ID) // Get user ID from context
newReport := models.Report{
ID: uuid.NewString(),
Title: request.Title,
Description: request.Description,
Document: request.Document,
ProjectName: request.ProjectName,
ProjectDomain: request.ProjectDomain,
CreatedBy: userId,
EndTime: time.Now().Add(time.Second * 300),
}
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&newReport).Error; err != nil {
return fmt.Errorf("failed to insert report: %w", err)
}

// Insert tags
for _, tag := range request.Tags {
if err := tx.Create(&models.ReportTag{ReportID: newReport.ID, Tag: tag}).Error; err != nil {
return fmt.Errorf("failed to insert tag for report: %w", err)
}
}

// Insert images
for _, imageURL := range request.Images {
if err := tx.Create(&models.ReportImage{ReportID: newReport.ID, ImageURL: imageURL}).Error; err != nil {
return fmt.Errorf("failed to insert image for report: %w", err)
}
}

return nil
})

if err != nil {
logwrapper.Errorf("failed to create report: %s", err)
httpo.NewErrorResponse(http.StatusInternalServerError, "Failed to create report").SendD(c)
return
}

httpo.NewSuccessResponseP(200, "Report created successfully", newReport).SendD(c)
}
70 changes: 70 additions & 0 deletions api/v1/report/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package report

import (
"net/http"
"time"

"github.com/NetSepio/gateway/api/middleware/auth/paseto"
"github.com/NetSepio/gateway/config/dbconfig"
"github.com/NetSepio/gateway/models"
"github.com/TheLazarusNetwork/go-helpers/httpo"
"github.com/gin-gonic/gin"
)

// getReports fetches reports with optional filters
func getReports(c *gin.Context) {
var filter ReportFilter
if err := c.ShouldBindQuery(&filter); err != nil {
httpo.NewErrorResponse(http.StatusBadRequest, "Invalid query parameters").SendD(c)
return
}

db := dbconfig.GetDb()
userId := c.GetString(paseto.CTX_USER_ID)

// Query with vote counts
query := db.Debug().Model(&models.Report{}).
Select(`reports.*,
COUNT(DISTINCT CASE WHEN report_votes.vote_type = 'upvote' THEN report_votes.voter_id END) as upvotes,
COUNT(DISTINCT CASE WHEN report_votes.vote_type = 'downvote' THEN report_votes.voter_id END) as downvotes,
COUNT(DISTINCT CASE WHEN report_votes.vote_type = 'notsure' THEN report_votes.voter_id END) as notSure,
COUNT(DISTINCT report_votes.voter_id) as totalVotes,
reports.end_time,
(SELECT vote_type FROM report_votes WHERE report_id = reports.id AND voter_id = ?) as user_vote`, userId).
Joins("LEFT JOIN report_votes ON report_votes.report_id = reports.id").
Group("reports.id")

// Apply filters
if filter.Title != "" {
query = query.Where("title ILIKE ?", "%"+filter.Title+"%")
}
if filter.ProjectDomain != "" {
query = query.Where("project_domain = ?", filter.ProjectDomain)
}
if filter.ProjectName != "" {
query = query.Where("project_name = ?", filter.ProjectName)
}

// Execute query
var reportsWithVotes []ReportWithVotes
if err := query.Find(&reportsWithVotes).Error; err != nil {
httpo.NewErrorResponse(http.StatusInternalServerError, "Failed to fetch reports").SendD(c)
return
}

// Calculate status for each report
for i := range reportsWithVotes {
report := &reportsWithVotes[i]
if time.Now().After(report.EndTime) {
if float64(report.Upvotes)/float64(report.TotalVotes) >= 0.51 {
report.Status = "accepted"
} else {
report.Status = "rejected"
}
} else {
report.Status = "running"
}
}

httpo.NewSuccessResponseP(http.StatusOK, "Reports fetched successfully", reportsWithVotes).SendD(c)
}
17 changes: 17 additions & 0 deletions api/v1/report/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package report

import (
"github.com/NetSepio/gateway/api/middleware/auth/paseto"
"github.com/gin-gonic/gin"
)

func ApplyRoutes(r *gin.RouterGroup) {
report := r.Group("/report")
{
report.Use(paseto.PASETO(false))

report.POST("/", postReport) // Endpoint for creating a new report
report.GET("/", getReports) // Endpoint for retrieving reports with optional filters
report.POST("/vote", postReportVote) // Endpoint for voting on a report
}
}
45 changes: 45 additions & 0 deletions api/v1/report/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package report

import (
"time"

"github.com/google/uuid"
)

// ReportRequest defines the structure for report creation request
type ReportRequest struct {
Title string `json:"title" binding:"required"`
Description string `json:"description"`
Images []string `json:"image"`
Document string `json:"document"`
Category string `json:"category"`
Tags []string `json:"tags"`
ProjectName string `json:"projectName"`
ProjectDomain string `json:"projectDomain"`
}

// ReportFilter for query parameters
type ReportFilter struct {
Title string `form:"title"`
ProjectDomain string `form:"projectDomain"`
ProjectName string `form:"projectName"`
Accepted *bool `form:"accepted"`
}

// ReportWithVotes and calculated status
type ReportWithVotes struct {
ID uuid.UUID `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Document string `json:"document"`
ProjectName string `json:"projectName"`
ProjectDomain string `json:"projectDomain"`
CreatedBy uuid.UUID `json:"createdBy"`
EndTime time.Time `json:"endTime"`
Upvotes int `json:"upvotes"`
Downvotes int `json:"downvotes"`
NotSure int `json:"notSure"`
TotalVotes int `json:"totalVotes"`
Status string `json:"status"` // Calculated status
UserVote string `json:"userVote"`
}
66 changes: 66 additions & 0 deletions api/v1/report/vote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package report

import (
"errors"
"fmt"
"net/http"
"time"

"github.com/NetSepio/gateway/api/middleware/auth/paseto"
"github.com/NetSepio/gateway/config/dbconfig"
"github.com/NetSepio/gateway/models"
"github.com/NetSepio/gateway/util/pkg/logwrapper"
"github.com/TheLazarusNetwork/go-helpers/httpo"
"github.com/gin-gonic/gin"
"github.com/jackc/pgconn"
)

type ReportVoteRequest struct {
ReportID string `json:"reportId" binding:"required"`
VoteType string `json:"voteType" binding:"required,oneof=upvote downvote notsure"`
}

func postReportVote(c *gin.Context) {
var request ReportVoteRequest
if err := c.BindJSON(&request); err != nil {
httpo.NewErrorResponse(http.StatusBadRequest, fmt.Sprintf("Invalid request body: %s", err)).SendD(c)
return
}

db := dbconfig.GetDb()
userId := c.GetString(paseto.CTX_USER_ID) // Assuming user ID is in the context

// Check if the voting period has ended
var report models.Report
if err := db.Where("id = ?", request.ReportID).First(&report).Error; err != nil {
httpo.NewErrorResponse(http.StatusInternalServerError, "Failed to vote").SendD(c)
return
}

if time.Now().After(report.EndTime) {
httpo.NewErrorResponse(http.StatusBadRequest, "Voting period has ended").SendD(c)
return
}

// Insert or update the vote
newVote := models.ReportVote{
ReportID: request.ReportID,
VoterID: userId,
VoteType: request.VoteType,
}
err := db.Create(&newVote).Error
if err != nil {
var pgError *pgconn.PgError
if errors.As(err, &pgError) {
if pgError.Code == "23505" && pgError.ConstraintName == "report_votes_pkey" {
httpo.NewErrorResponse(http.StatusBadRequest, "You have already voted on this report").SendD(c)
return
}
}
logwrapper.Errorf("failed to record vote: %s", err)
httpo.NewErrorResponse(http.StatusInternalServerError, "Failed to record vote").SendD(c)
return
}

httpo.NewSuccessResponse(http.StatusOK, "Vote recorded successfully").SendD(c)
}
2 changes: 2 additions & 0 deletions api/v1/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/NetSepio/gateway/api/v1/getreviewerdetails"
"github.com/NetSepio/gateway/api/v1/getreviews"
"github.com/NetSepio/gateway/api/v1/profile"
"github.com/NetSepio/gateway/api/v1/report"
"github.com/NetSepio/gateway/api/v1/sotreus"
"github.com/NetSepio/gateway/api/v1/stats"
"github.com/NetSepio/gateway/api/v1/status"
Expand All @@ -36,6 +37,7 @@ func ApplyRoutes(r *gin.RouterGroup) {
getreviewerdetails.ApplyRoutes(v1)
sotreus.ApplyRoutes(v1)
domain.ApplyRoutes(v1)
report.ApplyRoutes(v1)
account.ApplyRoutes(v1)
}
}
3 changes: 1 addition & 2 deletions main.go
Copy link
Member

Choose a reason for hiding this comment

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

Whats with the hardcode here?

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/NetSepio/gateway/models/claims"
"github.com/NetSepio/gateway/util/pkg/auth"
"github.com/NetSepio/gateway/util/pkg/logwrapper"
"github.com/google/uuid"
)

func main() {
Expand All @@ -26,7 +25,7 @@ func main() {
if os.Getenv("DEBUG_MODE") == "true" {
newUser := &models.User{
WalletAddress: strings.ToLower("0x984185d39c67c954bd058beb619faf8929bb9349ef33c15102bdb982cbf7f18f"),
UserId: uuid.NewString(),
UserId: "fc8fe270-ce16-4df9-a17f-979bcd824e98",
}
if err := db.Create(newUser).Error; err != nil {
logwrapper.Warn(err)
Expand Down
32 changes: 32 additions & 0 deletions migrations/000009_create_report.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
CREATE TABLE public.reports (
id uuid PRIMARY KEY,
title text NOT NULL,
description text,
document text,
project_name text,
project_domain text,
status text CHECK (status IN ('accepted', 'rejected', 'running')),
created_by uuid REFERENCES public.users(user_id),
end_time timestamp with time zone,
created_at timestamp with time zone DEFAULT current_timestamp
);

CREATE TABLE public.report_tags (
report_id uuid REFERENCES public.reports(id),
tag text,
UNIQUE(report_id, tag)
);

CREATE TABLE public.report_images (
report_id uuid REFERENCES public.reports(id),
image_url text,
UNIQUE(report_id, image_url)
);

CREATE TABLE public.report_votes (
report_id uuid REFERENCES public.reports(id),
voter_id uuid REFERENCES public.users(user_id),
vote_type text CHECK (vote_type IN ('upvote', 'downvote', 'notsure')),
created_at timestamp with time zone DEFAULT current_timestamp,
PRIMARY KEY (report_id, voter_id)
);
34 changes: 34 additions & 0 deletions models/Report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package models

import (
"time"
)

type Report struct {
ID string `gorm:"type:uuid;primary_key;"`
Title string `gorm:"type:text;not null"`
Description string `gorm:"type:text"`
Document string `gorm:"type:text"`
ProjectName string `gorm:"type:text"`
ProjectDomain string `gorm:"type:text"`
CreatedBy string `gorm:"type:uuid"`
CreatedAt time.Time `gorm:"type:timestamp"`
EndTime time.Time `gorm:"type:timestamp"`
}

type ReportVote struct {
ReportID string `gorm:"type:uuid;primaryKey;"`
VoterID string `gorm:"type:uuid;primaryKey;"`
VoteType string `gorm:"type:text"`
CreatedAt time.Time `gorm:"type:timestamp with time zone"`
}

type ReportTag struct {
ReportID string `gorm:"type:uuid;"`
Tag string `gorm:"type:text;"`
}

type ReportImage struct {
ReportID string `gorm:"type:uuid;"`
ImageURL string `gorm:"type:text;"`
}
2 changes: 2 additions & 0 deletions models/claims/Claim.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package claims

import (
"fmt"
"time"

"github.com/NetSepio/gateway/config/dbconfig"
Expand All @@ -21,6 +22,7 @@ func (c CustomClaims) Valid() error {
if err := c.RegisteredClaims.Valid(); err != nil {
return err
}
fmt.Printf("c.UserId: %s\n", c.UserId)
err := db.Model(&models.User{}).Where("user_id = ?", c.UserId).First(&models.User{}).Error
if err != nil {
return err
Expand Down