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
2 changes: 2 additions & 0 deletions internal/api/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ var (
RespUnauthorized = []byte(`{"error": "unauthorized"}`)
RespFalseAuthentication = []byte(`{"error": "false authentication"}`)
RespPendingActivation = []byte(`{"error": "pending activation"}`)

RespInvalidID = []byte(`{"error": "invalid ID"}`)
)

type Error struct {
Expand Down
112 changes: 110 additions & 2 deletions internal/api/handlers/keyword/handler.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,117 @@
package keyword

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"

"github.com/go-chi/chi/v5"
v "github.com/go-playground/validator/v10"
"gorm.io/gorm"

e "web-scraper.dev/internal/api/errors"
"web-scraper.dev/internal/repository"
"web-scraper.dev/internal/utils/ctxutil"
l "web-scraper.dev/internal/utils/logger"

_ "web-scraper.dev/internal/model"
)

func Read(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("."))
type API struct {
db *repository.Db
logger *l.Logger
validator *v.Validate
}

func New(db *gorm.DB, logger *l.Logger, validator *v.Validate) *API {
return &API{
db: repository.New(db),
logger: logger,
validator: validator,
}
}

// GetKeywords godoc
// @summary Get the list of keywords
// @description Get the list of keywords uploaded by current user
// @tags keywords
//
// @router /keywords [GET]
// @accept json
// @produce json
// @security BearerToken
// @success 200 {array} model.KeywordDTO
// @failure 401 {object} e.Error
// @failure 500 {object} e.Error
func (a *API) GetKeywords(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
reqID, ctxUser := ctxutil.RequestID(ctx), ctxutil.UserFromCtx(ctx)

keywords, err := a.db.ListKeywordsByUserId(*ctxUser.ID)
if err != nil {
a.logger.Error().Str(l.KeyReqID, reqID).Err(err).Msg("")
e.ServerError(w, e.RespDBDataAccessFailure)
return
}

if keywords == nil {
fmt.Fprint(w, "[]")
return
}

dto := keywords.ToDTOs()
if err := json.NewEncoder(w).Encode(&dto); err != nil {
a.logger.Error().Str(l.KeyReqID, reqID).Err(err).Msg("")
e.ServerError(w, e.RespJSONEncodeFailure)
return
}
}

// GetKeyword godoc
// @summary Get the result of a keyword
// @description Get the result of a keyword uploaded by current user
// @tags keywords
//
// @router /keywords/{id} [GET]
// @accept json
// @produce json
// @security BearerToken
// @param id path string true "Payment ID in uuid format"
//
// @success 200 {object} model.KeywordDTO
// @failure 400 {object} e.Error
// @failure 401 {object} e.Error
// @failure 404
// @failure 500 {object} e.Error
func (a *API) GetKeyword(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
reqID, ctxUser := ctxutil.RequestID(ctx), ctxutil.UserFromCtx(ctx)

id, err := strconv.Atoi(chi.URLParam(r, "id"))
if err != nil || id < 1 {
e.BadRequest(w, e.RespInvalidID)
}

keyword, err := a.db.ReadKeywordByIdAndUserId(int64(id), *ctxUser.ID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
w.WriteHeader(http.StatusNotFound)
return
}

a.logger.Error().Str(l.KeyReqID, reqID).Err(err).Msg("")
e.ServerError(w, e.RespDBDataAccessFailure)
return
}

if err := json.NewEncoder(w).Encode(&keyword); err != nil {
a.logger.Error().Str(l.KeyReqID, reqID).Err(err).Msg("")
e.ServerError(w, e.RespJSONEncodeFailure)
return
}
}

func (a *API) UploadKeywords(w http.ResponseWriter, r *http.Request) {
}
6 changes: 5 additions & 1 deletion internal/api/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ func New(hd time.Duration, hdw time.Duration, db *gorm.DB, ml *mailer.Mailer, l
r.Route("/", func(r chi.Router) {
r.Use(middleware.JwtAuthentication)

r.Method(http.MethodGet, "/keywords", requestlog.NewHandler(keyword.Read, hd, l))
keywordAPI := keyword.New(db, l, v)
r.Method(http.MethodGet, "/keywords", requestlog.NewHandler(keywordAPI.GetKeywords, hd, l))
r.Method(http.MethodGet, "/keywords/{id}", requestlog.NewHandler(keywordAPI.GetKeyword, hd, l))

r.Method(http.MethodPost, "/keywords", requestlog.NewHandler(keywordAPI.UploadKeywords, hd, l))
})
})

Expand Down
50 changes: 50 additions & 0 deletions internal/model/keyword.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package model

import (
"github.com/google/uuid"
)

type Keywords []*Keyword

type Keyword struct {
Model2
UserID uuid.UUID
Keyword string
Status string
SearchEngine string
AdCount *int64
LinkCount *int64
HTMLContent *string
ErrorMessage *string
}

type KeywordDTO struct {
ID int64 `json:"id"`
Keyword string `json:"keyword"`
Status string `json:"status"`
AdCount *int64 `json:"adCount"`
LinkCount *int64 `json:"linkCount"`
HTMLContent *string `json:"htmlContent"`
ErrorMessage *string `json:"errorMessage"`
}

func (ks Keywords) ToDTOs() []*KeywordDTO {
result := make([]*KeywordDTO, len(ks))
for i, v := range ks {
result[i] = v.ToDTO()
}

return result
}

func (k *Keyword) ToDTO() *KeywordDTO {
return &KeywordDTO{
ID: k.ID,
Keyword: k.Keyword,
Status: k.Status,
AdCount: k.AdCount,
LinkCount: k.LinkCount,
HTMLContent: k.HTMLContent,
ErrorMessage: k.ErrorMessage,
}
}
6 changes: 6 additions & 0 deletions internal/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ type Model struct {
CreatedAt *time.Time `json:"-"`
UpdatedAt *time.Time `json:"-"`
}

type Model2 struct {
ID int64 `gorm:"primaryKey;autoIncrement"`
CreatedAt *time.Time `json:"-"`
UpdatedAt *time.Time `json:"-"`
}
3 changes: 3 additions & 0 deletions internal/repository/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,7 @@ type DB interface {

CreateOrUpdateUserActivationTokenByUserId(uat *model.UserActivationToken) error
DeleteUserActivationTokenByUserId(userId uuid.UUID) error

ListKeywordsByUserId(userID uuid.UUID) (model.Keywords, error)
ReadKeywordByIdAndUserId(id uuid.UUID, userId uuid.UUID) (*model.Keyword, error)
}
23 changes: 23 additions & 0 deletions internal/repository/keyword.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package repository

import (
"github.com/google/uuid"

"web-scraper.dev/internal/model"
)

func (db *Db) ListKeywordsByUserId(userID uuid.UUID) (model.Keywords, error) {
keywords := make([]*model.Keyword, 0)
if err := db.Where("user_id = ?", userID).Order("created_at desc, updated_at desc").Find(&keywords).Error; err != nil {
return nil, err
}
return keywords, nil
}

func (db *Db) ReadKeywordByIdAndUserId(id int64, userId uuid.UUID) (*model.Keyword, error) {
keyword := &model.Keyword{}
if err := db.Where("id = ? AND user_id = ?", id, userId).First(keyword).Error; err != nil {
return nil, err
}
return keyword, nil
}
98 changes: 98 additions & 0 deletions openapi-v3.1.0.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ components:
type: array
uniqueItems: false
type: object
web-scraper_dev_internal_model.KeywordDTO:
properties:
adCount:
type: integer
errorMessage:
type: string
htmlContent:
type: string
id:
type: integer
keyword:
type: string
linkCount:
type: integer
status:
type: string
type: object
externalDocs:
description: ""
url: ""
Expand All @@ -60,6 +77,87 @@ info:
version: "1.0"
openapi: 3.1.0
paths:
/keywords:
get:
description: Get the list of keywords uploaded by current user
requestBody:
content:
application/json:
schema:
type: object
responses:
"200":
content:
application/json:
schema:
items:
$ref: '#/components/schemas/web-scraper_dev_internal_model.KeywordDTO'
type: array
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/web-scraper_dev_internal_api_errors.Error'
description: Unauthorized
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/web-scraper_dev_internal_api_errors.Error'
description: Internal Server Error
security:
- BearerToken: []
summary: Get the list of keywords
tags:
- keywords
/keywords/{id}:
get:
description: Get the result of a keyword uploaded by current user
parameters:
- description: Payment ID in uuid format
in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
type: object
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/web-scraper_dev_internal_model.KeywordDTO'
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/web-scraper_dev_internal_api_errors.Error'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/web-scraper_dev_internal_api_errors.Error'
description: Unauthorized
"404":
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/web-scraper_dev_internal_api_errors.Error'
description: Internal Server Error
security:
- BearerToken: []
summary: Get the result of a keyword
tags:
- keywords
/users/activate:
post:
description: |-
Expand Down
Loading