feat: imoplement basic structure

This commit is contained in:
Alphyron 2025-02-03 21:33:58 +01:00
parent c312239de8
commit eb1daac82f
13 changed files with 717 additions and 3 deletions

View File

@ -1,5 +1,132 @@
package main package main
import (
"git.anthrove.art/Anthrove/gorse-playground/internal/logic"
"git.anthrove.art/Anthrove/gorse-playground/pkg/models"
"git.anthrove.art/Anthrove/gorse-playground/pkg/utils"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
func main() { func main() {
router := gin.Default()
store := cookie.NewStore([]byte("secret"))
router.Use(sessions.Sessions("mysession", store))
router.Use(func(c *gin.Context) {
session := sessions.Default(c)
userid := session.Get("userid")
if userid == nil && c.FullPath() != "/login" {
c.Redirect(http.StatusFound, "/login")
c.Abort()
return
}
c.Next()
})
router.LoadHTMLGlob("web/template/**/*")
router.GET("/login", func(c *gin.Context) {
c.HTML(http.StatusOK, "login.gohtml", nil)
})
router.POST("/login", func(c *gin.Context) {
session := sessions.Default(c)
userID := c.PostForm("userid")
// Handle the user ID as needed
session.Set("userid", userID)
err := session.Save()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Redirect(http.StatusFound, "/profile")
})
router.GET("/logout", func(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
c.Redirect(http.StatusFound, "/login")
})
router.GET("/profile", func(c *gin.Context) {
session := sessions.Default(c)
userid := session.Get("userid")
c.HTML(http.StatusOK, "profile.gohtml", gin.H{"userid": userid})
})
router.POST("/scrape", func(c *gin.Context) {
session := sessions.Default(c)
userid := session.Get("userid")
useridInt, err := strconv.Atoi(userid.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"convert error": err.Error()})
return
}
allFaves, err := logic.GetAllFavorites(c, useridInt)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"get faves error": err.Error()})
return
}
items, faves, err := utils.ConvertE6ToGorse(userid.(string), allFaves)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"convert to gorse error": err.Error()})
return
}
err = logic.UpsertUser(c, models.GorseUser{
Comment: "",
Labels: nil,
Subscribe: nil,
UserId: userid.(string),
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"upsert user error": err.Error()})
return
}
err = logic.UpsertItems(c, items)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"upsert items error": err.Error()})
return
}
err = logic.UpsertFavorites(c, faves)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"upsert faves error": err.Error()})
}
c.JSON(http.StatusOK, gin.H{"items": items})
})
router.GET("/post", func(c *gin.Context) {
session := sessions.Default(c)
userid := session.Get("userid")
page := c.DefaultQuery("page", "1")
pageInt, err := strconv.Atoi(page)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
recs, err := logic.GetUserFavorites(c, userid.(string), pageInt)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
}
c.HTML(http.StatusOK, "post.gohtml", gin.H{"recs": recs, "next_page": pageInt + 1, "last_page": pageInt - 1})
})
router.Run(":8080")
} }

253
config/gorse/config.toml Normal file
View File

@ -0,0 +1,253 @@
[database]
# The database for caching, support Redis, MySQL, Postgres and MongoDB:
# redis://<user>:<password>@<host>:<port>/<db_number>
# rediss://<user>:<password>@<host>:<port>/<db_number>
# redis+cluster://<user>:<password>@<host1>:<port1>,<host2>:<port2>,...,<hostN>:<portN>
# postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full
# postgresql://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full
# mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
# mongodb+srv://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
cache_store = "redis://localhost:6379/0"
# The database for persist data, support MySQL, Postgres, ClickHouse and MongoDB:
# mysql://[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
# postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full
# postgresql://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full
# clickhouse://user:password@host[:port]/database?param1=value1&...&paramN=valueN
# chhttp://user:password@host[:port]/database?param1=value1&...&paramN=valueN
# chhttps://user:password@host[:port]/database?param1=value1&...&paramN=valueN
# mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
# mongodb+srv://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
data_store = "mysql://gorse:gorse_pass@tcp(localhost:3306)/gorse"
# The naming prefix for tables (collections, keys) in databases. The default value is empty.
table_prefix = ""
# The naming prefix for tables (collections, keys) in cache storage databases. The default value is `table_prefix`.
cache_table_prefix = ""
# The naming prefix for tables (collections, keys) in data storage databases. The default value is `table_prefix`.
data_table_prefix = ""
[master]
# GRPC port of the master node. The default value is 8086.
port = 8086
# gRPC host of the master node. The default values is "0.0.0.0".
host = "0.0.0.0"
# HTTP port of the master node. The default values is 8088.
http_port = 8088
# HTTP host of the master node. The default values is "0.0.0.0".
http_host = "0.0.0.0"
# AllowedDomains is a list of allowed values for Http Origin.
# The list may contain the special wildcard string ".*" ; all is allowed
# If empty all are allowed.
http_cors_domains = []
# AllowedMethods is either empty or has a list of http methods names. Checking is case-insensitive.
http_cors_methods = []
# Number of working jobs in the master node. The default value is 1.
n_jobs = 12
# Meta information timeout. The default value is 10s.
meta_timeout = "10s"
# Username for the master node dashboard.
dashboard_user_name = ""
# Password for the master node dashboard.
dashboard_password = ""
# Secret key for admin APIs (SSL required).
admin_api_key = ""
[server]
# Default number of returned items. The default value is 10.
default_n = 10
# Secret key for RESTful APIs (SSL required).
api_key = ""
# Clock error in the cluster. The default value is 5s.
clock_error = "5s"
# Insert new users while inserting feedback. The default value is true.
auto_insert_user = true
# Insert new items while inserting feedback. The default value is true.
auto_insert_item = true
# Server-side cache expire time. The default value is 10s.
cache_expire = "10s"
[recommend]
# The cache size for recommended/popular/latest items. The default value is 10.
cache_size = 10
# Recommended cache expire time. The default value is 72h.
cache_expire = "72h"
[recommend.data_source]
# The feedback types for positive events.
positive_feedback_types = ["like"]
# The feedback types for read events.
read_feedback_types = []
# The time-to-live (days) of positive feedback, 0 means disabled. The default value is 0.
positive_feedback_ttl = 0
# The time-to-live (days) of items, 0 means disabled. The default value is 0.
item_ttl = 0
[recommend.popular]
# The time window of popular items. The default values is 4320h.
popular_window = "720h"
[recommend.user_neighbors]
# The type of neighbors for users. There are three types:
# similar: Neighbors are found by number of common labels.
# related: Neighbors are found by number of common liked items.
# auto: If a user have labels, neighbors are found by number of common labels.
# If this user have no labels, neighbors are found by number of common liked items.
# The default value is "auto".
neighbor_type = "similar"
# Enable approximate user neighbor searching using vector index. The default value is true.
enable_index = true
# Minimal recall for approximate user neighbor searching. The default value is 0.8.
index_recall = 0.8
# Maximal number of fit epochs for approximate user neighbor searching vector index. The default value is 3.
index_fit_epoch = 3
[recommend.item_neighbors]
# The type of neighbors for items. There are three types:
# similar: Neighbors are found by number of common labels.
# related: Neighbors are found by number of common users.
# auto: If a item have labels, neighbors are found by number of common labels.
# If this item have no labels, neighbors are found by number of common users.
# The default value is "auto".
neighbor_type = "auto"
# Enable approximate item neighbor searching using vector index. The default value is true.
enable_index = true
# Minimal recall for approximate item neighbor searching. The default value is 0.8.
index_recall = 0.8
# Maximal number of fit epochs for approximate item neighbor searching vector index. The default value is 3.
index_fit_epoch = 3
[recommend.collaborative]
# Enable approximate collaborative filtering recommend using vector index. The default value is true.
enable_index = true
# Minimal recall for approximate collaborative filtering recommend. The default value is 0.9.
index_recall = 0.9
# Maximal number of fit epochs for approximate collaborative filtering recommend vector index. The default value is 3.
index_fit_epoch = 3
# The time period for model fitting. The default value is "60m".
model_fit_period = "60m"
# The time period for model searching. The default value is "360m".
model_search_period = "360m"
# The number of epochs for model searching. The default value is 100.
model_search_epoch = 100
# The number of trials for model searching. The default value is 10.
model_search_trials = 10
# Enable searching models of different sizes, which consume more memory. The default value is false.
enable_model_size_search = false
[recommend.replacement]
# Replace historical items back to recommendations. The default value is false.
enable_replacement = false
# Decay the weights of replaced items from positive feedbacks. The default value is 0.8.
positive_replacement_decay = 0.8
# Decay the weights of replaced items from read feedbacks. The default value is 0.6.
read_replacement_decay = 0.6
[recommend.offline]
# The time period to check recommendation for users. The default values is 1m.
check_recommend_period = "1m"
# The time period to refresh recommendation for inactive users. The default values is 120h.
refresh_recommend_period = "24h"
# Enable latest recommendation during offline recommendation. The default value is false.
enable_latest_recommend = false
# Enable popular recommendation during offline recommendation. The default value is false.
enable_popular_recommend = false
# Enable user-based similarity recommendation during offline recommendation. The default value is false.
enable_user_based_recommend = true
# Enable item-based similarity recommendation during offline recommendation. The default value is false.
enable_item_based_recommend = true
# Enable collaborative filtering recommendation during offline recommendation. The default value is true.
enable_collaborative_recommend = false
# Enable click-though rate prediction during offline recommendation. Otherwise, results from multi-way recommendation
# would be merged randomly. The default value is false.
enable_click_through_prediction = false
# The explore recommendation method is used to inject popular items or latest items into recommended result:
# popular: Recommend popular items to cold-start users.
# latest: Recommend latest items to cold-start users.
# The default values is { popular = 0.0, latest = 0.0 }.
explore_recommend = { popular = 0.0, latest = 0.0 }
[recommend.online]
# The fallback recommendation method is used when cached recommendation drained out:
# item_based: Recommend similar items to cold-start users.
# popular: Recommend popular items to cold-start users.
# latest: Recommend latest items to cold-start users.
# Recommenders are used in order. The default values is ["latest"].
fallback_recommend = ["item_based"]
# The number of feedback used in fallback item-based similar recommendation. The default values is 10.
num_feedback_fallback_item_based = 10
[tracing]
# Enable tracing for REST APIs. The default value is false.
enable_tracing = false
# The type of tracing exporters should be one of "jaeger", "zipkin", "otlp" and "otlphttp". The default value is "jaeger".
exporter = "jaeger"
# The endpoint of tracing collector.
collector_endpoint = "http://localhost:14268/api/traces"
# The type of tracing sampler should be one of "always", "never" and "ratio". The default value is "always".
sampler = "always"
# The ratio of ratio based sampler. The default value is 1.
ratio = 1

View File

@ -0,0 +1,86 @@
services:
redis:
image: docker.dragonflydb.io/dragonflydb/dragonfly
restart: unless-stopped
ulimits:
memlock: -1
ports:
- 6379:6379
volumes:
- redis:/data
postgres:
image: postgres:17
restart: unless-stopped
ports:
- 5432:5432
environment:
POSTGRES_DB: gorse
POSTGRES_USER: gorse
POSTGRES_PASSWORD: gorse
volumes:
- pg_data:/var/lib/postgresql/data
worker:
image: zhenghaoz/gorse-worker
restart: unless-stopped
command: >
--master-host master --master-port 8086
--http-host 0.0.0.0 --http-port 8089
--log-path /var/log/gorse/worker.log
--cache-path /var/lib/gorse/worker_cache.data
volumes:
- gorse_log:/var/log/gorse # Mount log files in volume.
- worker_data:/var/lib/gorse # Mount cache files in volume.
depends_on:
- master
server:
image: zhenghaoz/gorse-server
restart: unless-stopped
ports:
- 8087:8087 # RESTful APIs and Prometheus metrics export port.
command: >
--master-host master --master-port 8086
--http-host 0.0.0.0 --http-port 8087
--log-path /var/log/gorse/server.log
--cache-path /var/lib/gorse/server_cache.data
volumes:
- gorse_log:/var/log/gorse # Mount log files in volume.
- server_data:/var/lib/gorse # Mount cache files in volume.
depends_on:
- master
master:
image: zhenghaoz/gorse-master
restart: unless-stopped
ports:
- 8086:8086 # HTTP port
- 8088:8088 # gRPC port
environment:
# Use Redis as cache storage backend.
GORSE_CACHE_STORE: redis://redis:6379
# Use MySQL as data storage backend.
GORSE_DATA_STORE: postgres://gorse:gorse@postgres:5432/gorse?sslmode=disable
command: >
-c /etc/gorse/config.toml
--log-path /var/log/gorse/master.log
--cache-path /var/lib/gorse/master_cache.data
volumes:
# Mount the configuration file.
- ../config/gorse/config.toml:/etc/gorse/config.toml
# Mount log files in volume.
- gorse_log:/var/log/gorse
# Mount cache files in volume.
- master_data:/var/lib/gorse
depends_on:
- redis
- postgres
volumes:
pg_data:
redis:
gorse_log:
worker_data:
server_data:
master_data:

32
go.mod
View File

@ -4,7 +4,39 @@ go 1.23.0
require ( require (
github.com/anthrove/openapi-e621-go v1.1.2 // indirect github.com/anthrove/openapi-e621-go v1.1.2 // indirect
github.com/bytedance/sonic v1.12.8 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/caarlos0/env/v11 v11.3.1 // indirect github.com/caarlos0/env/v11 v11.3.1 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sessions v1.0.2 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.13.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.9.0 // indirect
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/validator.v2 v2.0.1 // indirect gopkg.in/validator.v2 v2.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

88
go.sum
View File

@ -1,8 +1,96 @@
github.com/anthrove/openapi-e621-go v1.1.2 h1:x+zYGb9zi5QgHvlhtnwhOJtFgC7DONBfgH7Bc2TDw8w= github.com/anthrove/openapi-e621-go v1.1.2 h1:x+zYGb9zi5QgHvlhtnwhOJtFgC7DONBfgH7Bc2TDw8w=
github.com/anthrove/openapi-e621-go v1.1.2/go.mod h1:CET4X8hWfC1O1APRB2pHjbP4FwRyde6t4EaimijJCG0= github.com/anthrove/openapi-e621-go v1.1.2/go.mod h1:CET4X8hWfC1O1APRB2pHjbP4FwRyde6t4EaimijJCG0=
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA= github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U= github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY= gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8= gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -1,6 +1,6 @@
package config package config
type E621 struct { type E621 struct {
ApiKey string `env:"E621_API_KEY"` ApiKey string `env:"E621_API_KEY,required"`
Username string `env:"E621_USERNAME"` Username string `env:"E621_USERNAME,required"`
} }

View File

@ -1,6 +1,6 @@
package config package config
type Gorse struct { type Gorse struct {
Endpoint string `env:"GORSE_ENDPOINT"` Endpoint string `env:"GORSE_ENDPOINT,required"`
ApiKey string `env:"GORSE_APIKEY"` ApiKey string `env:"GORSE_APIKEY"`
} }

View File

@ -10,6 +10,8 @@ import (
"golang.org/x/time/rate" "golang.org/x/time/rate"
"log" "log"
"net/http" "net/http"
_ "github.com/joho/godotenv/autoload"
) )
var e621Config config.E621 var e621Config config.E621

View File

@ -9,6 +9,9 @@ import (
"github.com/caarlos0/env/v11" "github.com/caarlos0/env/v11"
"log" "log"
"net/http" "net/http"
"strconv"
_ "github.com/joho/godotenv/autoload"
) )
var gorseConfig config.Gorse var gorseConfig config.Gorse
@ -25,6 +28,44 @@ func UpsertUser(ctx context.Context, user models.GorseUser) error {
return executeRequest(ctx, http.MethodPost, "/api/user", user) return executeRequest(ctx, http.MethodPost, "/api/user", user)
} }
func UpsertItems(ctx context.Context, items []models.GorseItem) error {
return executeRequest(ctx, http.MethodPost, "/api/items", items)
}
func UpsertFavorites(ctx context.Context, items []models.GorseFavorite) error {
return executeRequest(ctx, http.MethodPost, "/api/feedback", items)
}
func GetUserFavorites(ctx context.Context, userid string, page int) ([]string, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", gorseConfig.Endpoint+"/api/recommend/"+userid, nil)
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Set("name", "50")
q.Set("offset", strconv.Itoa((page-1)*50))
req = req.WithContext(ctx)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", gorseConfig.ApiKey)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var items []string
err = json.NewDecoder(resp.Body).Decode(&items)
if err != nil {
return nil, err
}
return items, nil
}
func executeRequest(ctx context.Context, method, url string, data any) error { func executeRequest(ctx context.Context, method, url string, data any) error {
jsonData, err := json.Marshal(data) jsonData, err := json.Marshal(data)

41
pkg/utils/convert.go Normal file
View File

@ -0,0 +1,41 @@
package utils
import (
"git.anthrove.art/Anthrove/gorse-playground/pkg/models"
"github.com/anthrove/openapi-e621-go"
"strconv"
)
func ConvertE6ToGorse(userId string, posts []openapi.Post) ([]models.GorseItem, []models.GorseFavorite, error) {
faves := make([]models.GorseFavorite, 0)
items := make([]models.GorseItem, 0)
for _, post := range posts {
tags := make([]string, 0)
tags = append(tags, post.Tags.Artist...)
tags = append(tags, post.Tags.Character...)
tags = append(tags, post.Tags.Contributor...)
tags = append(tags, post.Tags.Copyright...)
tags = append(tags, post.Tags.General...)
tags = append(tags, post.Tags.Invalid...)
tags = append(tags, post.Tags.Lore...)
tags = append(tags, post.Tags.Meta...)
tags = append(tags, post.Tags.Species...)
items = append(items, models.GorseItem{
Comment: post.Description,
IsHidden: post.Flags.Deleted,
ItemId: strconv.Itoa(int(post.Id)),
Labels: tags,
Timestamp: post.CreatedAt.String(),
})
faves = append(faves, models.GorseFavorite{
Comment: post.Description,
FeedbackType: "like",
ItemId: strconv.Itoa(int(post.Id)),
UserId: userId,
})
}
return items, faves, nil
}

View File

@ -0,0 +1,12 @@
<html lang="en">
<head>
<title>Login Page</title>
</head>
<body>
<form method='post' action='/login'>
<label for="userid">User-ID:</label>
<input type="text" id="userid" name="userid">
<button type="submit">Submit</button>
</form>
</body>
</html>

View File

@ -0,0 +1,21 @@
<html lang="en">
<head>
<title>Recommendations Page</title>
</head>
<body>
<h1>Available Recommondations</h1>
<div>
{{range .recs}}
<a href="https://e621.net/posts/{{.}}" target="_blank">{{.}}</a><br>
{{end}}
<form method="get">
<input value="{{ .last_page }}" style="display: none" name="page">
<button type="submit">Last Page</button>
</form>
<form method="get">
<input value="{{ .next_page }}" style="display: none" name="page">
<button type="submit">Next Page</button>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,11 @@
<html lang="en">
<head>
<title>Profile Page</title>
</head>
<body>
<h1>User {{ .userid }}</h1>
<form method="POST" action="/scrape">
<button type="submit">Scrape</button>
</form>
</body>
</html>