api-call-system-#10 (#13)
Co-authored-by: Fenpaws <soxx@fenpa.ws> Reviewed-on: anthrove/e621-to-graph#13 Reviewed-by: SoXX <fenpaws@noreply.localhost> Co-authored-by: Lennard Brinkhaus <lennard.brinkhaus@noreply.localhost> Co-committed-by: Lennard Brinkhaus <lennard.brinkhaus@noreply.localhost>
This commit is contained in:
parent
f4b73034b8
commit
31bd0cf639
@ -6,12 +6,14 @@ import (
|
|||||||
"e621_to_neo4j/e621"
|
"e621_to_neo4j/e621"
|
||||||
"e621_to_neo4j/services"
|
"e621_to_neo4j/services"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserHandler is the handler for the user API
|
// UserHandler is the handler for the user API
|
||||||
func UserHandler(ctx context.Context, graphConnection database.GraphConnection, e621Client *e621.Client) func(response http.ResponseWriter, request *http.Request) {
|
func UserHandler(ctx context.Context, graphConnection database.GraphConnection, e621Client *e621.Client) func(response http.ResponseWriter, request *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Println("Request")
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
fmt.Fprintf(w, "Only POST requests are allowed")
|
fmt.Fprintf(w, "Only POST requests are allowed")
|
||||||
|
@ -12,6 +12,10 @@ type neo4jConnection struct {
|
|||||||
driver neo4j.DriverWithContext
|
driver neo4j.DriverWithContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewNeo4JConnection() database.GraphConnection {
|
||||||
|
return &neo4jConnection{}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *neo4jConnection) CheckUserToPostLink(ctx context.Context, e621PostID int64, e621UserID int64) (bool, error) {
|
func (c *neo4jConnection) CheckUserToPostLink(ctx context.Context, e621PostID int64, e621UserID int64) (bool, error) {
|
||||||
return RelationshipCheckUserToPost(ctx, c.driver, e621PostID, e621UserID)
|
return RelationshipCheckUserToPost(ctx, c.driver, e621PostID, e621UserID)
|
||||||
}
|
}
|
||||||
@ -57,10 +61,6 @@ func (c *neo4jConnection) Connect(ctx context.Context, endpoint string, username
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNeo4JConnection() database.GraphConnection {
|
|
||||||
return &neo4jConnection{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func useConsoleLogger(level neo4j.LogLevel) func(config *config.Config) {
|
func useConsoleLogger(level neo4j.LogLevel) func(config *config.Config) {
|
||||||
return func(config *config.Config) {
|
return func(config *config.Config) {
|
||||||
config.Log = neo4j.ConsoleLogger(level)
|
config.Log = neo4j.ConsoleLogger(level)
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
package e621
|
package e621
|
||||||
|
|
||||||
import (
|
import "golang.org/x/time/rate"
|
||||||
"e621_to_neo4j/utils"
|
|
||||||
"golang.org/x/time/rate"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
baseURL = "https://e621.net"
|
baseURL = "https://e621.net"
|
||||||
@ -12,20 +8,18 @@ const (
|
|||||||
|
|
||||||
// Client represents the e621 API client.
|
// Client represents the e621 API client.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
apiKey string
|
apiKey string
|
||||||
username string
|
username string
|
||||||
client *http.Client
|
scheduler *Scheduler
|
||||||
limiter *rate.Limiter
|
|
||||||
queue *utils.Queue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new e621 API client.
|
// NewClient creates a new e621 API client.
|
||||||
func NewClient(apiKey string, username string) *Client {
|
func NewClient(apiKey string, username string) *Client {
|
||||||
|
scheduler := NewScheduler()
|
||||||
|
scheduler.SetLimiter(rate.NewLimiter(1, 2))
|
||||||
return &Client{
|
return &Client{
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
username: username,
|
username: username,
|
||||||
client: &http.Client{},
|
scheduler: scheduler,
|
||||||
limiter: rate.NewLimiter(1, 2),
|
|
||||||
queue: &utils.Queue{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,14 @@
|
|||||||
package e621
|
package e621
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"e621_to_neo4j/e621/models"
|
"e621_to_neo4j/e621/models"
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetFavorites retrieves all favorites from the e621 API.
|
// GetFavorites retrieves all favorites from the e621 API.
|
||||||
func (c *Client) GetFavorites(user models.E621User) ([]models.Post, error) {
|
func (c *Client) GetFavorites(_ context.Context, user models.E621User, page int64) func() (models.PostResponseWrapper, error) {
|
||||||
page := 1
|
URIPath := fmt.Sprintf("favorites.json?user_id=%d&limit=%d&page=%d", user.ID, 320, page)
|
||||||
var allFavorites []models.Post
|
e621Task := NewE621ApiTask[models.PostResponseWrapper](URIPath)
|
||||||
var URIPath string
|
return Schedule[models.PostResponseWrapper](c.scheduler, e621Task, c.username, c.apiKey)
|
||||||
|
|
||||||
for {
|
|
||||||
URIPath = fmt.Sprintf("favorites.json?user_id=%d&limit=%d&page=%d", user.ID, 320, page)
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"id": user.ID,
|
|
||||||
"fav_page": page,
|
|
||||||
"uri": URIPath,
|
|
||||||
}).Debug("Requesting API for favorites")
|
|
||||||
favorite, err := ExecuteGetAPIRequest[models.PostResponseWrapper](c, URIPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append the fetched posts to the result slice
|
|
||||||
allFavorites = append(allFavorites, favorite.Posts...)
|
|
||||||
|
|
||||||
// If no more posts are returned, return the accumulated favorites
|
|
||||||
if len(favorite.Posts) == 0 {
|
|
||||||
return allFavorites, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the last post ID for the next page request
|
|
||||||
page += 1
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,39 @@
|
|||||||
package e621
|
package e621
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"e621_to_neo4j/utils"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
log "github.com/sirupsen/logrus"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExecuteGetAPIRequest[dataType any](c *Client, URIPath string) (*dataType, error) {
|
func ExecuteGetAPIRequest(schedulerTask utils.SchedulerTask) {
|
||||||
var err error
|
var err error
|
||||||
c.limiter.Wait(context.Background())
|
log.Debug("executing scheduler task")
|
||||||
url := fmt.Sprintf("%s/%s", baseURL, URIPath)
|
|
||||||
|
url := fmt.Sprintf("%s/%s", baseURL, schedulerTask.UriPath())
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
schedulerTask.SendError(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "FavGetter (by Selloo)")
|
req.Header.Set("User-Agent", "FavGetter (by Selloo)")
|
||||||
req.Header.Add("Accept", "application/json")
|
req.Header.Add("Accept", "application/json")
|
||||||
req.SetBasicAuth(c.username, c.apiKey)
|
req.SetBasicAuth(schedulerTask.BasicAuth())
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
client := http.Client{}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
schedulerTask.SendStatusCode(resp.StatusCode)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("failed to retrieve posts: %s", resp.Status)
|
schedulerTask.SendStatusCode(resp.StatusCode)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var r dataType
|
schedulerTask.SendResponse(resp)
|
||||||
err = json.Unmarshal(body, &r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &r, nil
|
|
||||||
}
|
}
|
||||||
|
61
e621/scheduler.go
Normal file
61
e621/scheduler.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package e621
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"e621_to_neo4j/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Scheduler struct {
|
||||||
|
queue utils.Queue
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScheduler() *Scheduler {
|
||||||
|
scheduler := &Scheduler{
|
||||||
|
queue: utils.NewQueue(),
|
||||||
|
limiter: nil,
|
||||||
|
}
|
||||||
|
go scheduler.StartExecutionHandler()
|
||||||
|
return scheduler
|
||||||
|
}
|
||||||
|
|
||||||
|
func Schedule[T utils.DataType](s *Scheduler, t utils.Task[T], username string, apiKey string) func() (T, error) {
|
||||||
|
channel := make(chan utils.DataResponse[T])
|
||||||
|
schedulerTask := NewSchedulerTaskImpl[T](t, channel, username, apiKey)
|
||||||
|
log.Debug("Psuh task")
|
||||||
|
err := s.queue.Push(schedulerTask)
|
||||||
|
log.Debug("Element pushed")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return func() (T, error) {
|
||||||
|
var nil T
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func() (T, error) {
|
||||||
|
data := <-channel
|
||||||
|
return data.Data, data.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scheduler) SetLimiter(limiter *rate.Limiter) {
|
||||||
|
s.limiter = limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scheduler) StartExecutionHandler() {
|
||||||
|
for {
|
||||||
|
if s.limiter != nil {
|
||||||
|
s.limiter.Wait(context.Background())
|
||||||
|
}
|
||||||
|
s.queue.WaitForElement()
|
||||||
|
log.Debug("element found")
|
||||||
|
task, err := s.queue.Pop()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ExecuteGetAPIRequest(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
e621/schedulertask.go
Normal file
42
e621/schedulertask.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package e621
|
||||||
|
|
||||||
|
import (
|
||||||
|
"e621_to_neo4j/utils"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type schedulerTaskImpl[T utils.DataType] struct {
|
||||||
|
task utils.Task[T]
|
||||||
|
channel chan utils.DataResponse[T]
|
||||||
|
username string
|
||||||
|
apiKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImpl[T]) BasicAuth() (string, string) {
|
||||||
|
return s.username, s.apiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSchedulerTaskImpl[T utils.DataType](task utils.Task[T], channel chan utils.DataResponse[T], username string, apiKey string) utils.SchedulerTask {
|
||||||
|
return &schedulerTaskImpl[T]{
|
||||||
|
task: task,
|
||||||
|
channel: channel,
|
||||||
|
username: username,
|
||||||
|
apiKey: apiKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImpl[T]) SendError(err error) {
|
||||||
|
s.channel <- s.task.HandleError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImpl[T]) UriPath() string {
|
||||||
|
return s.task.UriPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImpl[T]) SendStatusCode(statusCode int) {
|
||||||
|
s.channel <- s.task.HandleStatusCode(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImpl[T]) SendResponse(response *http.Response) {
|
||||||
|
s.channel <- s.task.HandleResponse(response)
|
||||||
|
}
|
56
e621/task.go
Normal file
56
e621/task.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package e621
|
||||||
|
|
||||||
|
import (
|
||||||
|
"e621_to_neo4j/utils"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type e621APITask[T utils.DataType] struct {
|
||||||
|
uri string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e e621APITask[T]) UriPath() string {
|
||||||
|
return e.uri
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e e621APITask[T]) HandleStatusCode(statusCode int) utils.DataResponse[T] {
|
||||||
|
var err error
|
||||||
|
switch statusCode {
|
||||||
|
case 421:
|
||||||
|
err = utils.RateLimitReachedError{}
|
||||||
|
case 424:
|
||||||
|
err = utils.InvalidParametersError{}
|
||||||
|
case 520:
|
||||||
|
err = utils.OriginConnectionTimeOutError{}
|
||||||
|
case 522:
|
||||||
|
err = utils.OriginConnectionTimeOutError{}
|
||||||
|
case 524:
|
||||||
|
err = utils.OriginConnectionTimeOutError{}
|
||||||
|
case 525:
|
||||||
|
err = utils.SSLHandshakeFailedError{}
|
||||||
|
default:
|
||||||
|
err = utils.StatusCodesToError(statusCode)
|
||||||
|
}
|
||||||
|
return utils.DataResponse[T]{Error: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e e621APITask[T]) HandleResponse(responseData *http.Response) utils.DataResponse[T] {
|
||||||
|
var data T
|
||||||
|
err := json.NewDecoder(responseData.Body).Decode(&data)
|
||||||
|
defer responseData.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return utils.DataResponse[T]{Error: err}
|
||||||
|
}
|
||||||
|
return utils.DataResponse[T]{Data: data}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e e621APITask[T]) HandleError(error error) utils.DataResponse[T] {
|
||||||
|
return utils.DataResponse[T]{Error: error}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewE621ApiTask[T utils.DataType](uri string) utils.Task[T] {
|
||||||
|
return &e621APITask[T]{
|
||||||
|
uri: uri,
|
||||||
|
}
|
||||||
|
}
|
@ -3,19 +3,11 @@ package e621
|
|||||||
import (
|
import (
|
||||||
"e621_to_neo4j/e621/models"
|
"e621_to_neo4j/e621/models"
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetUserInfo retrieves the users information from e621 API.
|
// GetUserInfo retrieves the users information from e621 API.
|
||||||
func (c *Client) GetUserInfo(username string) (models.E621User, error) {
|
func (c *Client) GetUserInfo(username string) func() (models.E621User, error) {
|
||||||
URIPath := fmt.Sprintf("users/%s.json", username)
|
URIPath := fmt.Sprintf("users/%s.json", username)
|
||||||
log.WithFields(log.Fields{
|
e621Task := NewE621ApiTask[models.E621User](URIPath)
|
||||||
"username": username,
|
return Schedule[models.E621User](c.scheduler, e621Task, c.username, c.apiKey)
|
||||||
"uri": URIPath,
|
|
||||||
}).Debug("Requesting API for user details")
|
|
||||||
user, err := ExecuteGetAPIRequest[models.E621User](c, URIPath)
|
|
||||||
if err != nil {
|
|
||||||
return models.E621User{}, err
|
|
||||||
}
|
|
||||||
return *user, nil
|
|
||||||
}
|
}
|
||||||
|
1
runMemgraphDev.cmd
Normal file
1
runMemgraphDev.cmd
Normal file
@ -0,0 +1 @@
|
|||||||
|
docker run -it -p 7687:7687 -p 7444:7444 -p 3000:3000 -v mg_lib:/var/lib/memgraph memgraph/memgraph-platform
|
@ -12,9 +12,13 @@ import (
|
|||||||
|
|
||||||
func ScrapeUser(ctx context.Context, graphConnection database.GraphConnection, e621Client e621.Client, username string) error {
|
func ScrapeUser(ctx context.Context, graphConnection database.GraphConnection, e621Client e621.Client, username string) error {
|
||||||
var err error
|
var err error
|
||||||
|
var page int64 = 1
|
||||||
|
var allFavorites []models.Post
|
||||||
|
|
||||||
e621User, err := e621Client.GetUserInfo(username)
|
AwaitE621User := e621Client.GetUserInfo(username)
|
||||||
|
e621User, err := AwaitE621User()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Info(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,21 +46,47 @@ func ScrapeUser(ctx context.Context, graphConnection database.GraphConnection, e
|
|||||||
"id": e621User.ID,
|
"id": e621User.ID,
|
||||||
}).Info("Getting favorites for user")
|
}).Info("Getting favorites for user")
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
userFavorites, err := e621Client.GetFavorites(e621User)
|
|
||||||
|
for {
|
||||||
|
AwaitE621Favorites := e621Client.GetFavorites(ctx, e621User, page)
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"id": e621User.ID,
|
||||||
|
"fav_page": page,
|
||||||
|
}).Debug("Requesting API for favorites")
|
||||||
|
|
||||||
|
favoritesPage, err := AwaitE621Favorites()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the fetched posts to the result slice
|
||||||
|
allFavorites = append(allFavorites, favoritesPage.Posts...)
|
||||||
|
|
||||||
|
// If no more posts are returned, return the accumulated favorites
|
||||||
|
if len(favoritesPage.Posts) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the last post ID for the next page request
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"user": e621User.Name,
|
"user": e621User.Name,
|
||||||
"id": e621User.ID,
|
"id": e621User.ID,
|
||||||
"post_amount": len(userFavorites),
|
"post_amount": len(allFavorites),
|
||||||
"scrape_time": time.Since(start),
|
"scrape_time": time.Since(start),
|
||||||
}).Info("Getting favorites for user")
|
}).Info("Getting favorites for user")
|
||||||
|
|
||||||
startUploadPosts := time.Now()
|
startUploadPosts := time.Now()
|
||||||
|
|
||||||
// Uploads all Tags, Posts as Nodes to Neo4j
|
// Uploads all Tags, Posts as Nodes to Neo4j
|
||||||
for i, post := range userFavorites {
|
for i, post := range allFavorites {
|
||||||
if exists, err := graphConnection.CheckUserToPostLink(ctx, post.ID, e621User.ID); err == nil && exists {
|
if exists, err := graphConnection.CheckUserToPostLink(ctx, post.ID, e621User.ID); err == nil && exists {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"user": e621User.Name,
|
"user": e621User.Name,
|
||||||
@ -77,7 +107,7 @@ func ScrapeUser(ctx context.Context, graphConnection database.GraphConnection, e
|
|||||||
"user": e621User.Name,
|
"user": e621User.Name,
|
||||||
"id": e621User.ID,
|
"id": e621User.ID,
|
||||||
"post_number": i,
|
"post_number": i,
|
||||||
"post_amount": len(userFavorites),
|
"post_amount": len(allFavorites),
|
||||||
"post_id": post.ID,
|
"post_id": post.ID,
|
||||||
"upload_time": time.Since(start),
|
"upload_time": time.Since(start),
|
||||||
}).Debug("Uploading post")
|
}).Debug("Uploading post")
|
||||||
@ -122,7 +152,7 @@ func ScrapeUser(ctx context.Context, graphConnection database.GraphConnection, e
|
|||||||
"user": e621User.Name,
|
"user": e621User.Name,
|
||||||
"id": e621User.ID,
|
"id": e621User.ID,
|
||||||
"post_number": i,
|
"post_number": i,
|
||||||
"post_amount": len(userFavorites),
|
"post_amount": len(allFavorites),
|
||||||
"post_id": post.ID,
|
"post_id": post.ID,
|
||||||
"upload_time": time.Since(start),
|
"upload_time": time.Since(start),
|
||||||
}).Debug("Making relationship")
|
}).Debug("Making relationship")
|
||||||
@ -258,7 +288,7 @@ func uploadCopyrightTagRelationship(ctx context.Context, graphConnection databas
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// log.Printf("Created PostToTagRelationship for post: %d to copyrigh tag: %s", post.ID, copyrightTag)
|
// log.Printf("Created PostToTagRelationship for post: %d to copyright tag: %s", post.ID, copyrightTag)
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
105
utils/error.go
Normal file
105
utils/error.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func StatusCodesToError(statusCode int) error {
|
||||||
|
var err error
|
||||||
|
switch statusCode {
|
||||||
|
case 403:
|
||||||
|
err = AccessDeniedError{}
|
||||||
|
case 404:
|
||||||
|
err = NotFoundError{}
|
||||||
|
case 412:
|
||||||
|
err = PreconditionFailedError{}
|
||||||
|
case 421:
|
||||||
|
err = RateLimitReachedError{}
|
||||||
|
case 424:
|
||||||
|
err = InvalidParametersError{}
|
||||||
|
case 500:
|
||||||
|
err = InternalServerError{}
|
||||||
|
case 502:
|
||||||
|
err = BadGatewayError{}
|
||||||
|
case 503:
|
||||||
|
err = ServiceUnavailableError{}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unhandels status code: %d", statusCode)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessDeniedError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ AccessDeniedError) Error() string {
|
||||||
|
return "access denied"
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotFoundError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ NotFoundError) Error() string {
|
||||||
|
return "not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreconditionFailedError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ PreconditionFailedError) Error() string {
|
||||||
|
return "precondition failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
type RateLimitReachedError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ RateLimitReachedError) Error() string {
|
||||||
|
return "rate limit reached"
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvalidParametersError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ InvalidParametersError) Error() string {
|
||||||
|
return "invalide parameters"
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalServerError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ InternalServerError) Error() string {
|
||||||
|
return "internal server error"
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadGatewayError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ BadGatewayError) Error() string {
|
||||||
|
return "bad gateway"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceUnavailableError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ ServiceUnavailableError) Error() string {
|
||||||
|
return "service unavailable"
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnknownError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ UnknownError) Error() string {
|
||||||
|
return "unknown error"
|
||||||
|
}
|
||||||
|
|
||||||
|
type OriginConnectionTimeOutError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ OriginConnectionTimeOutError) Error() string {
|
||||||
|
return "origin connection time-out"
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSLHandshakeFailedError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ SSLHandshakeFailedError) Error() string {
|
||||||
|
return "ssl handshake failed"
|
||||||
|
}
|
@ -2,32 +2,43 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Task struct {
|
|
||||||
URIPath string `json:"url,omitempty" :"url"`
|
|
||||||
Methode string `json:"method,omitempty" :"method"`
|
|
||||||
Channel chan any `:"channel"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Queue struct {
|
type Queue struct {
|
||||||
elements []Task
|
tasks []SchedulerTask
|
||||||
|
notifyChannel chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (queue *Queue) Pop() (Task, error) {
|
func NewQueue() Queue {
|
||||||
if len(queue.elements) == 0 {
|
return Queue{
|
||||||
return Task{}, errors.New("try to remove an element of a empty queue")
|
notifyChannel: make(chan bool),
|
||||||
}
|
}
|
||||||
task := queue.elements[0]
|
}
|
||||||
queue.elements = queue.elements[1:]
|
|
||||||
|
// WaitForElement need to be called everytime before Popping an Element!
|
||||||
|
// Also, it is required to have this function called in a separate go-routine because Push use the NotifyChannel in the
|
||||||
|
// routine. So the thread waits, till WaitForElement pulls the Item from the channel
|
||||||
|
// FIXME this should be fixed in future. require more discussion what is the best way.
|
||||||
|
func (queue *Queue) WaitForElement() {
|
||||||
|
log.Debug("waiting for element")
|
||||||
|
_ = <-queue.notifyChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (queue *Queue) Pop() (SchedulerTask, error) {
|
||||||
|
if len(queue.tasks) == 0 {
|
||||||
|
return nil, errors.New("try to remove an element of a empty queue")
|
||||||
|
}
|
||||||
|
task := queue.tasks[0]
|
||||||
|
queue.tasks = queue.tasks[1:]
|
||||||
return task, nil
|
return task, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (queue *Queue) Push(task Task) error {
|
func (queue *Queue) Push(task SchedulerTask) error {
|
||||||
empty := Task{}
|
if task == nil {
|
||||||
if task == empty {
|
|
||||||
return errors.New("try to add task but task is empty")
|
return errors.New("try to add task but task is empty")
|
||||||
}
|
}
|
||||||
queue.elements = append(queue.elements, task)
|
queue.tasks = append(queue.tasks, task)
|
||||||
|
queue.notifyChannel <- true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,65 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type schedulerTaskImplDummy struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImplDummy) BasicAuth() (string, string) {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImplDummy) UriPath() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImplDummy) SendError(_ error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImplDummy) SendStatusCode(_ int) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s schedulerTaskImplDummy) SendResponse(_ *http.Response) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func TestQueue_Pop(t *testing.T) {
|
func TestQueue_Pop(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
elements []Task
|
elements []SchedulerTask
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fields fields
|
fields fields
|
||||||
want Task
|
want SchedulerTask
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Pop element of empty list",
|
name: "Pop element of empty list",
|
||||||
fields: fields{},
|
fields: fields{},
|
||||||
want: Task{},
|
want: nil,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Pop element of a filled list with three elements",
|
name: "Pop element of a filled list with three elements",
|
||||||
fields: fields{elements: []Task{
|
fields: fields{elements: []SchedulerTask{
|
||||||
{URIPath: "https://e621.net0....", Methode: "GET", Channel: nil},
|
schedulerTaskImplDummy{},
|
||||||
{URIPath: "https://e621.net1....", Methode: "GET", Channel: nil},
|
schedulerTaskImplDummy{},
|
||||||
{URIPath: "https://e621.net2....", Methode: "GET", Channel: nil},
|
schedulerTaskImplDummy{},
|
||||||
}},
|
}},
|
||||||
want: Task{
|
want: schedulerTaskImplDummy{},
|
||||||
URIPath: "https://e621.net0....",
|
|
||||||
Methode: "GET",
|
|
||||||
Channel: nil,
|
|
||||||
},
|
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
queue := &Queue{
|
queue := &Queue{
|
||||||
elements: tt.fields.elements,
|
tasks: tt.fields.elements,
|
||||||
}
|
}
|
||||||
got, err := queue.Pop()
|
got, err := queue.Pop()
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
@ -55,42 +75,32 @@ func TestQueue_Pop(t *testing.T) {
|
|||||||
|
|
||||||
func TestQueue_Push(t *testing.T) {
|
func TestQueue_Push(t *testing.T) {
|
||||||
t.Run("Push tasks to empty queue", func(t *testing.T) {
|
t.Run("Push tasks to empty queue", func(t *testing.T) {
|
||||||
queue := Queue{elements: []Task{}}
|
queue := Queue{tasks: []SchedulerTask{}, notifyChannel: make(chan bool)}
|
||||||
task := Task{
|
go queue.WaitForElement()
|
||||||
URIPath: "http://e621.net0....",
|
task := schedulerTaskImplDummy{}
|
||||||
Methode: "GET",
|
|
||||||
Channel: nil,
|
|
||||||
}
|
|
||||||
err := queue.Push(task)
|
err := queue.Push(task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Push() error = %v", err)
|
t.Errorf("Push() error = %v", err)
|
||||||
}
|
}
|
||||||
if len(queue.elements) != 1 {
|
if len(queue.tasks) != 1 {
|
||||||
t.Errorf("Push() error = queue is not one")
|
t.Errorf("Push() error = queue is not one")
|
||||||
}
|
}
|
||||||
if queue.elements[0] != task {
|
if queue.tasks[0] != task {
|
||||||
t.Errorf("Push() error = wrong queue task in queue")
|
t.Errorf("Push() error = wrong queue task in queue")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("Push tasks to filled queue", func(t *testing.T) {
|
t.Run("Push tasks to filled queue", func(t *testing.T) {
|
||||||
queue := Queue{elements: []Task{{
|
queue := Queue{tasks: []SchedulerTask{schedulerTaskImplDummy{}}, notifyChannel: make(chan bool)}
|
||||||
URIPath: "http://e621.net0....",
|
go queue.WaitForElement()
|
||||||
Methode: "GET",
|
task := schedulerTaskImplDummy{}
|
||||||
Channel: nil,
|
|
||||||
}}}
|
|
||||||
task := Task{
|
|
||||||
URIPath: "http://e621.net1....",
|
|
||||||
Methode: "GET",
|
|
||||||
Channel: nil,
|
|
||||||
}
|
|
||||||
err := queue.Push(task)
|
err := queue.Push(task)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Push() error = %v", err)
|
t.Errorf("Push() error = %v", err)
|
||||||
}
|
}
|
||||||
if len(queue.elements) != 2 {
|
if len(queue.tasks) != 2 {
|
||||||
t.Errorf("Push() error = queue is not two")
|
t.Errorf("Push() error = queue is not two")
|
||||||
}
|
}
|
||||||
if queue.elements[1] != task {
|
if queue.tasks[1] != task {
|
||||||
t.Errorf("Push() error = wrong queue task in queue")
|
t.Errorf("Push() error = wrong queue task in queue")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
30
utils/scheduler.go
Normal file
30
utils/scheduler.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"e621_to_neo4j/e621/models"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataResponse[T DataType] struct {
|
||||||
|
Data T
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataType interface {
|
||||||
|
models.E621User | models.PostResponseWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
type Task[T DataType] interface {
|
||||||
|
UriPath() string
|
||||||
|
HandleError(error error) DataResponse[T]
|
||||||
|
HandleStatusCode(statusCode int) DataResponse[T]
|
||||||
|
HandleResponse(responseData *http.Response) DataResponse[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchedulerTask interface {
|
||||||
|
UriPath() string
|
||||||
|
SendStatusCode(statusCode int)
|
||||||
|
SendError(err error)
|
||||||
|
SendResponse(response *http.Response)
|
||||||
|
BasicAuth() (string, string)
|
||||||
|
}
|
Reference in New Issue
Block a user