mirror of
https://codeberg.org/pluja/kycnot.me
synced 2025-01-23 21:01:15 -05:00
rename api routes, set rate limit, add generic api
This commit is contained in:
parent
f0b85c80e3
commit
53a8bff21e
@ -37,7 +37,7 @@ function startPow() {
|
|||||||
|
|
||||||
spinner.classList.add('hidden');
|
spinner.classList.add('hidden');
|
||||||
|
|
||||||
const url = `/api/v1/pow/verify/${id}/${nonce}`;
|
const url = `/api/g/pow/verify/${id}/${nonce}`;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
@ -47,11 +47,11 @@ function startPow() {
|
|||||||
element.classList.add('bg-lime-700');
|
element.classList.add('bg-lime-700');
|
||||||
element.classList.add('disabled');
|
element.classList.add('disabled');
|
||||||
text.innerText = '✅ Verified!';
|
text.innerText = '✅ Verified!';
|
||||||
|
|
||||||
// Show submit button
|
// Show submit button
|
||||||
const submit = document.querySelector('#submit-btn');
|
const submit = document.querySelector('#submit-btn');
|
||||||
submit.classList.remove('hidden');
|
submit.classList.remove('hidden');
|
||||||
|
|
||||||
// Set pow-nonce form input value to nonce
|
// Set pow-nonce form input value to nonce
|
||||||
const nonceInput = document.querySelector('#pow-nonce');
|
const nonceInput = document.querySelector('#pow-nonce');
|
||||||
nonceInput.value = nonce;
|
nonceInput.value = nonce;
|
||||||
|
179
src/server/api_generic.go
Normal file
179
src/server/api_generic.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/freetype"
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/font/gofont/gomonobold"
|
||||||
|
|
||||||
|
"pluja.dev/kycnot.me/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) handleVerifyPow(c iris.Context) {
|
||||||
|
// Get id, nonce and proof from the request
|
||||||
|
id := c.Params().Get("id")
|
||||||
|
nonce := c.Params().Get("nonce")
|
||||||
|
|
||||||
|
// Verify the proof of work
|
||||||
|
valid := s.PowChallenger.PowVerifyProof(id, nonce)
|
||||||
|
|
||||||
|
// Return the result
|
||||||
|
c.JSON(iris.Map{
|
||||||
|
"valid": valid,
|
||||||
|
"status": iris.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxies the request to the logo URL of a service by its ID
|
||||||
|
func (s *Server) handleApiPicture(c iris.Context) {
|
||||||
|
id := c.Params().Get("id")
|
||||||
|
|
||||||
|
service, err := database.Pb.GetServiceById(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Could not get service")
|
||||||
|
respondWithPlaceholder(c, "?")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.LogoURL == "" {
|
||||||
|
log.Debug().Msgf("Image %s not found in cache", service.ID)
|
||||||
|
respondWithPlaceholder(c, service.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if imageData, err := s.Cache.Get(fmt.Sprintf("img-%s", service.ID)); err == nil {
|
||||||
|
log.Debug().Msgf("Found image %s in cache", service.ID)
|
||||||
|
ctt, _ := s.Cache.Get(fmt.Sprintf("ctt-%s", service.ID))
|
||||||
|
c.ContentType(string(ctt))
|
||||||
|
c.StatusCode(iris.StatusOK)
|
||||||
|
c.Write(imageData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msgf("Image %s not found in cache", service.ID)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", service.LogoURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to create HTTP request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.3")
|
||||||
|
req.Header.Set("Referer", service.Urls[0])
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil || resp.StatusCode != http.StatusOK {
|
||||||
|
log.Error().Err(err).Msg("Could not get image")
|
||||||
|
respondWithPlaceholder(c, service.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bodyBytes, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Could not read response body")
|
||||||
|
respondWithPlaceholder(c, service.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if bodyBytes == nil {
|
||||||
|
log.Error().Msg("Could not read response body")
|
||||||
|
respondWithPlaceholder(c, service.Name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Cache.Set(fmt.Sprintf("img-%s", service.ID), bodyBytes)
|
||||||
|
s.Cache.Set(fmt.Sprintf("ctt-%s", service.ID), []byte(resp.Header.Get("Content-Type")))
|
||||||
|
|
||||||
|
c.ContentType(resp.Header.Get("Content-Type"))
|
||||||
|
c.StatusCode(iris.StatusOK)
|
||||||
|
c.Write(bodyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func respondWithPlaceholder(c iris.Context, serviceName string) {
|
||||||
|
log.Debug().Msgf("Generating placeholder for %s", serviceName)
|
||||||
|
const width, height = 250, 250
|
||||||
|
const margin = 15
|
||||||
|
firstLetter := strings.ToUpper(string(serviceName[0]))
|
||||||
|
|
||||||
|
// Create an image
|
||||||
|
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||||
|
|
||||||
|
// Set background to black
|
||||||
|
draw.Draw(img, img.Bounds(), image.NewUniform(color.Black), image.Point{}, draw.Src)
|
||||||
|
|
||||||
|
// Load the monospace font
|
||||||
|
f, err := truetype.Parse(gomonobold.TTF)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("could not parse font")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize freetype context
|
||||||
|
ctx := freetype.NewContext()
|
||||||
|
ctx.SetDPI(72)
|
||||||
|
ctx.SetFont(f)
|
||||||
|
|
||||||
|
// Parse hex color and set text color
|
||||||
|
var r, g, b uint8
|
||||||
|
hexColor := "#84cc16"
|
||||||
|
_, err = fmt.Sscanf(hexColor, "#%02x%02x%02x", &r, &g, &b)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Invalid hex color")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
textColor := color.RGBA{R: r, G: g, B: b, A: 255}
|
||||||
|
ctx.SetSrc(image.NewUniform(textColor))
|
||||||
|
|
||||||
|
ctx.SetClip(img.Bounds())
|
||||||
|
ctx.SetDst(img)
|
||||||
|
|
||||||
|
// Dynamically calculate the maximum font size
|
||||||
|
var fontSize float64
|
||||||
|
var textWidth, textHeight int
|
||||||
|
for fontSize = 205; fontSize > 10; fontSize -= 0.5 {
|
||||||
|
ctx.SetFontSize(fontSize)
|
||||||
|
opts := truetype.Options{Size: fontSize}
|
||||||
|
face := truetype.NewFace(f, &opts)
|
||||||
|
textWidth = font.MeasureString(face, firstLetter).Round()
|
||||||
|
ascent, descent := face.Metrics().Ascent, face.Metrics().Descent
|
||||||
|
textHeight = (ascent + descent).Ceil()
|
||||||
|
|
||||||
|
if textWidth <= width-margin*2 && textHeight <= height-margin*2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate position for the text
|
||||||
|
x := (width - textWidth) / 2
|
||||||
|
y := (height-textHeight)/2 + textHeight - int(ctx.PointToFixed(fontSize*0.2)>>6)
|
||||||
|
pt := freetype.Pt(x, y)
|
||||||
|
|
||||||
|
// Draw the text
|
||||||
|
if _, err := ctx.DrawString(firstLetter, pt); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to draw string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode to PNG
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := png.Encode(&buf, img); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to encode image")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ContentType("image/png")
|
||||||
|
c.StatusCode(iris.StatusOK)
|
||||||
|
c.Write(buf.Bytes())
|
||||||
|
}
|
23
src/server/api_v1.go
Normal file
23
src/server/api_v1.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"pluja.dev/kycnot.me/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) handleApiService(c iris.Context) {
|
||||||
|
serviceName := strings.ToLower(c.Params().Get("name"))
|
||||||
|
|
||||||
|
service, err := database.Pb.GetServiceByNameOrUrl(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Could not get service %v from database", serviceName)
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the service as JSON
|
||||||
|
c.JSON(service)
|
||||||
|
}
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/iris-contrib/middleware/cors"
|
"github.com/iris-contrib/middleware/cors"
|
||||||
"github.com/kataras/iris/v12"
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/kataras/iris/v12/middleware/rate"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"pluja.dev/kycnot.me/config"
|
"pluja.dev/kycnot.me/config"
|
||||||
@ -101,14 +102,24 @@ func (s *Server) RegisterRoutes() {
|
|||||||
s.Router.Post("/new/service", s.handlePostRequestServiceForm)
|
s.Router.Post("/new/service", s.handlePostRequestServiceForm)
|
||||||
|
|
||||||
// API routes
|
// API routes
|
||||||
api_v1 := s.Router.Party("/api/v1")
|
genericApi := s.Router.Party("/api/g")
|
||||||
api_v1.Get("/pow/verify/{id}/{nonce:int}", s.handleVerifyPow)
|
{
|
||||||
api_v1.Get("/picture/{id}", s.handleApiPicture)
|
genericApi.Get("/pow/verify/{id}/{nonce:int}", s.handleVerifyPow)
|
||||||
|
genericApi.Get("/picture/{id}", s.handleApiPicture)
|
||||||
|
}
|
||||||
|
|
||||||
|
v1Api := s.Router.Party("/api/v1")
|
||||||
|
{
|
||||||
|
limitV1 := rate.Limit(rate.Every(1*time.Minute), 5, rate.PurgeEvery(time.Minute, 5*time.Minute))
|
||||||
|
v1Api.Use(limitV1)
|
||||||
|
v1Api.Get("/service/{name:string}", s.handleApiService)
|
||||||
|
v1Api.Get("/service/{name:string}/summary", s.handleScoreSummary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RegisterViews() {
|
func (s *Server) RegisterViews() {
|
||||||
// Use blocks as the templating engine
|
// Use blocks as the templating engine
|
||||||
blocks := iris.Blocks("./frontend/templates", ".gohtml")
|
blocks := iris.Blocks("./frontend/templates", ".html")
|
||||||
if config.Conf.Dev {
|
if config.Conf.Dev {
|
||||||
blocks.Reload(true)
|
blocks.Reload(true)
|
||||||
}
|
}
|
||||||
|
344
src/server/web_handlers.go
Normal file
344
src/server/web_handlers.go
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"pluja.dev/kycnot.me/config"
|
||||||
|
"pluja.dev/kycnot.me/database"
|
||||||
|
"pluja.dev/kycnot.me/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) handleIndex(c iris.Context) {
|
||||||
|
nokycPf := []string{
|
||||||
|
"KYC-Free Crypto Freedom.",
|
||||||
|
"Goodbye KYC, hello privacy.",
|
||||||
|
"KYC is a scam. Don't fall for it.",
|
||||||
|
}
|
||||||
|
|
||||||
|
t := c.URLParam("t")
|
||||||
|
q := c.URLParam("q")
|
||||||
|
|
||||||
|
// Currencies
|
||||||
|
xmr := c.URLParam("xmr")
|
||||||
|
btc := c.URLParam("btc")
|
||||||
|
ln := c.URLParam("ln")
|
||||||
|
cash := c.URLParam("cash")
|
||||||
|
fiat := c.URLParam("fiat")
|
||||||
|
|
||||||
|
filters := []string{"(listed=true && pending=false)"}
|
||||||
|
|
||||||
|
if t != "" && t != "all" {
|
||||||
|
filters = append(filters, fmt.Sprintf("(type='%v')", t))
|
||||||
|
}
|
||||||
|
|
||||||
|
if q != "" {
|
||||||
|
filters = append(
|
||||||
|
filters,
|
||||||
|
fmt.Sprintf("(name~'%v' || description~'%v' || tags~'%v')", q, q, q),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if xmr != "" {
|
||||||
|
filters = append(filters, fmt.Sprintf("(xmr=%v)", xmr == "on"))
|
||||||
|
}
|
||||||
|
if btc != "" {
|
||||||
|
filters = append(filters, fmt.Sprintf("(btc=%v)", btc == "on"))
|
||||||
|
}
|
||||||
|
if ln != "" {
|
||||||
|
filters = append(filters, fmt.Sprintf("(lightning=%v)", ln == "on"))
|
||||||
|
}
|
||||||
|
if cash != "" {
|
||||||
|
filters = append(filters, fmt.Sprintf("(cash=%v)", cash == "on"))
|
||||||
|
}
|
||||||
|
if fiat != "" {
|
||||||
|
filters = append(filters, fmt.Sprintf("(fiat=%v)", fiat == "on"))
|
||||||
|
}
|
||||||
|
|
||||||
|
services, err := database.Pb.GetServices(
|
||||||
|
fmt.Sprintf("(%v)", strings.Join(filters, " && ")),
|
||||||
|
"-score,@random",
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Could not get services from Pocketbase")
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ViewLayout("main")
|
||||||
|
data := iris.Map{
|
||||||
|
"Title": "Home",
|
||||||
|
"Filters": map[string]string{
|
||||||
|
"Type": t,
|
||||||
|
"Query": q,
|
||||||
|
"Xmr": xmr,
|
||||||
|
"Btc": btc,
|
||||||
|
"Ln": ln,
|
||||||
|
"Cash": cash,
|
||||||
|
"Fiat": fiat,
|
||||||
|
},
|
||||||
|
"Announcement": *database.Pb.GetAnnouncement(),
|
||||||
|
"Current": "index",
|
||||||
|
"Services": services,
|
||||||
|
"RandomPitch": nokycPf[rand.Intn(len(nokycPf))],
|
||||||
|
}
|
||||||
|
if err := c.View("index", data); err != nil {
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleService(c iris.Context) {
|
||||||
|
serviceName := strings.ToLower(c.Params().Get("name"))
|
||||||
|
|
||||||
|
service, err := database.Pb.GetServiceByNameOrUrl(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Could not get service %v from database", serviceName)
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Compute score in background when needed!
|
||||||
|
// Update score in background
|
||||||
|
//upd := c.URLParam("update", "")
|
||||||
|
//if upd == "true" {
|
||||||
|
// go utils.UpdateScore(service)
|
||||||
|
//}
|
||||||
|
//utils.ComputeScore(service)
|
||||||
|
|
||||||
|
c.ViewLayout("main")
|
||||||
|
data := iris.Map{
|
||||||
|
"Title": fmt.Sprintf("%v | Service", serviceName),
|
||||||
|
"Service": service,
|
||||||
|
"Attributes": service.Expand["attributes"],
|
||||||
|
}
|
||||||
|
if err := c.View("service", data); err != nil {
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleScoreSummary(c iris.Context) {
|
||||||
|
serviceName := strings.ToLower(c.Params().Get("name"))
|
||||||
|
|
||||||
|
service, err := database.Pb.GetServiceByNameOrUrl(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Could not get service %v from database", serviceName)
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
summary := utils.ScoreSummary(service)
|
||||||
|
|
||||||
|
c.Text(summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleAttribute(c iris.Context) {
|
||||||
|
attributeId := strings.ToLower(c.Params().Get("id"))
|
||||||
|
|
||||||
|
// Get service from database by name
|
||||||
|
attribute, err := database.Pb.GetAttribute(attributeId)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Could not get attribute %v from database", attributeId)
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all services that have this attribute
|
||||||
|
services, err := database.Pb.GetServices(
|
||||||
|
fmt.Sprintf("(attributes~'%v')", attributeId),
|
||||||
|
"score",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Could not get services with attribute %v from database", attributeId)
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ViewLayout("main")
|
||||||
|
data := iris.Map{
|
||||||
|
"Title": fmt.Sprintf("%v", attribute.Title),
|
||||||
|
"Attribute": attribute,
|
||||||
|
"Services": services,
|
||||||
|
}
|
||||||
|
if err := c.View("attribute", data); err != nil {
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleRequestServiceForm(c iris.Context) {
|
||||||
|
challenge, id, difficulty, err := s.PowChallenger.PowGenerateChallenge(16)
|
||||||
|
if err != nil {
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes, err := database.Pb.GetAttributes("", "-rating")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Could not get attributes from database")
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ViewLayout("main")
|
||||||
|
data := iris.Map{
|
||||||
|
"Title": "Request a service",
|
||||||
|
"Current": "request",
|
||||||
|
"Pow": map[string]interface{}{
|
||||||
|
"Challenge": challenge,
|
||||||
|
"Difficulty": difficulty,
|
||||||
|
"Id": id,
|
||||||
|
},
|
||||||
|
"Attributes": attributes,
|
||||||
|
"Error": c.URLParam("error"),
|
||||||
|
"Message": c.URLParam("message"),
|
||||||
|
}
|
||||||
|
if err := c.View("request_service", data); err != nil {
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleAbout(c iris.Context) {
|
||||||
|
c.ViewLayout("main")
|
||||||
|
data := iris.Map{
|
||||||
|
"Title": "About",
|
||||||
|
"Current": "about",
|
||||||
|
"Btc": config.Conf.Donations.Btc,
|
||||||
|
"Xmr": config.Conf.Donations.Xmr,
|
||||||
|
"Lnn": config.Conf.Donations.Lnn,
|
||||||
|
}
|
||||||
|
if err := c.View("about", data); err != nil {
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handlePending(c iris.Context) {
|
||||||
|
services, err := database.Pb.GetServices("pending=true", "score")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Could not get services from database")
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ViewLayout("main")
|
||||||
|
data := iris.Map{
|
||||||
|
"Title": "Pending",
|
||||||
|
"Services": services,
|
||||||
|
}
|
||||||
|
if err := c.View("pending", data); err != nil {
|
||||||
|
c.HTML("<h3>%s</h3>", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestFormData struct {
|
||||||
|
Type string `form:"type"`
|
||||||
|
Category string `form:"category"`
|
||||||
|
Name string `form:"name"`
|
||||||
|
Description string `form:"description"`
|
||||||
|
Urls string `form:"urls"`
|
||||||
|
LogoUrl string `form:"logo_url"`
|
||||||
|
TosUrls string `form:"tos_urls"`
|
||||||
|
OnionUrls string `form:"onion_urls"`
|
||||||
|
Tags string `form:"tags"`
|
||||||
|
Xmr bool `form:"xmr"`
|
||||||
|
Btc bool `form:"btc"`
|
||||||
|
Ln bool `form:"ln"`
|
||||||
|
Fiat bool `form:"fiat"`
|
||||||
|
Cash bool `form:"cash"`
|
||||||
|
KYCLevel int `form:"kyc_level"`
|
||||||
|
Attributes []string `form:"attributes"`
|
||||||
|
PowNonce string `form:"pow-nonce"`
|
||||||
|
PowId string `form:"pow-id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handlePostRequestServiceForm(c iris.Context) {
|
||||||
|
log.Info().Msg("Handling request service form")
|
||||||
|
log.Debug().Msg("Handling request service form")
|
||||||
|
var data RequestFormData
|
||||||
|
if err := c.ReadForm(&data); err != nil {
|
||||||
|
log.Debug().Err(err).Msg("Could not parse form data")
|
||||||
|
c.Redirect("/request/service?error=Invalid%20Form", iris.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Nonce: %v, ID: %v", data.PowNonce, data.PowId)
|
||||||
|
if !s.PowChallenger.PowVerifyProof(data.PowId, data.PowNonce) {
|
||||||
|
log.Debug().Msg("Invalid PoW")
|
||||||
|
c.Redirect("/request/service?error=Invalid%20Captcha", iris.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.KYCLevel < 0 || data.KYCLevel >= 4 {
|
||||||
|
log.Debug().Msgf("Invalid KYC Level value: %v", c.FormValue("kyc_level"))
|
||||||
|
c.Redirect("/request/service?error=Invalid%20KYC%20Level", iris.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Attributes) < 3 {
|
||||||
|
log.Debug().Msgf("Invalid number of attributes: %v", len(data.Attributes))
|
||||||
|
c.Redirect("/request/service?error=You%20must%20select%20at%20least%203%20attributes", iris.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var service database.Service
|
||||||
|
// Parse tags
|
||||||
|
ts := strings.ReplaceAll(data.Tags, " ", ",")
|
||||||
|
// Remove trailing commas
|
||||||
|
ts = strings.TrimSuffix(ts, ",")
|
||||||
|
// Remove duplicate commas
|
||||||
|
ts = strings.ReplaceAll(ts, ",,", ",")
|
||||||
|
// Remove leading commas
|
||||||
|
ts = strings.TrimPrefix(ts, ",")
|
||||||
|
// Convert to lowercase
|
||||||
|
ts = strings.ToLower(ts)
|
||||||
|
// Create tags array
|
||||||
|
tags := strings.Split(ts, ",")
|
||||||
|
|
||||||
|
service.Tags = tags
|
||||||
|
service.Name = strings.ToLower(data.Name)
|
||||||
|
service.Description = data.Description
|
||||||
|
service.LogoURL = utils.UrlParser(data.LogoUrl)
|
||||||
|
service.Urls = utils.UrlListParser(data.Urls)
|
||||||
|
service.TosUrls = utils.UrlListParser(data.TosUrls)
|
||||||
|
service.OnionUrls = utils.UrlListParser(data.OnionUrls)
|
||||||
|
service.Xmr = data.Xmr
|
||||||
|
service.Btc = data.Btc
|
||||||
|
service.Lightning = data.Ln
|
||||||
|
service.Fiat = data.Fiat
|
||||||
|
service.Cash = data.Cash
|
||||||
|
service.Pending = true
|
||||||
|
service.Listed = false
|
||||||
|
service.Type = data.Type
|
||||||
|
|
||||||
|
if service.Type == "service" {
|
||||||
|
service.Category = data.Category
|
||||||
|
} else {
|
||||||
|
service.Category = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
service.KycLevel = data.KYCLevel
|
||||||
|
service.Attributes = data.Attributes
|
||||||
|
|
||||||
|
service.Score = utils.ComputeScore(&service)
|
||||||
|
|
||||||
|
// Save service to database
|
||||||
|
err := database.Pb.CreateService(service)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Could not save service to database")
|
||||||
|
c.Redirect("/request/service?error=internal-error", iris.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Update score in background
|
||||||
|
// go utils.UpdateScore(sv)
|
||||||
|
c.Redirect("/request/service?message=Success!", iris.StatusSeeOther)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user