diff --git a/src/server/handlers_api.go b/src/server/handlers_api.go
deleted file mode 100644
index a7bdcca..0000000
--- a/src/server/handlers_api.go
+++ /dev/null
@@ -1,179 +0,0 @@
-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())
-}
diff --git a/src/server/handlers_web.go b/src/server/handlers_web.go
deleted file mode 100644
index 219fc5c..0000000
--- a/src/server/handlers_web.go
+++ /dev/null
@@ -1,324 +0,0 @@
-package server
-
-import (
- "fmt"
- "math/rand"
- "strings"
-
- "github.com/kataras/iris/v12"
- "github.com/rs/zerolog/log"
-
- "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("
%s
", 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,
- },
- "Current": "index",
- "Services": services,
- "RandomPitch": nokycPf[rand.Intn(len(nokycPf))],
- }
- if err := c.View("index", data); err != nil {
- c.HTML("%s
", 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("%s
", 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("%s
", err.Error())
- return
- }
-}
-
-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("%s
", 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("%s
", 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("%s
", err.Error())
- return
- }
-}
-
-func (s *Server) handleRequestServiceForm(c iris.Context) {
- challenge, id, difficulty, err := s.PowChallenger.PowGenerateChallenge(16)
- if err != nil {
- c.HTML("%s
", 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("%s
", 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("%s
", err.Error())
- return
- }
-}
-
-func (s *Server) handleAbout(c iris.Context) {
- c.ViewLayout("main")
- data := iris.Map{
- "Title": "About",
- "Current": "about",
- }
- if err := c.View("about", data); err != nil {
- c.HTML("%s
", 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("%s
", err.Error())
- return
- }
-
- c.ViewLayout("main")
- data := iris.Map{
- "Title": "Pending",
- "Services": services,
- }
- if err := c.View("pending", data); err != nil {
- c.HTML("%s
", 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)
-}