Skip to content

Commit

Permalink
Pixel Shield (#428)
Browse files Browse the repository at this point in the history
* feat: shield ui flow

* feat: added shield UI and multicall to register shield

* fix: avoid empty pixel call

* feat: improve call

* fix: allow only one pixel selection

* fix: remove unsed import
  • Loading branch information
addegbenga authored Jan 24, 2025
1 parent af23819 commit e914250
Show file tree
Hide file tree
Showing 10 changed files with 783 additions and 206 deletions.
1 change: 1 addition & 0 deletions backend/cmd/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func main() {
indexer.InitIndexerRoutes()
routes.InitWebsocketRoutes()
routes.InitNFTStaticRoutes()
indexer.StartMessageProcessor()

core.AFKBackend.Start(core.AFKBackend.BackendConfig.Port)
}
3 changes: 2 additions & 1 deletion backend/postgres/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ CREATE TABLE Pixels (
position integer NOT NULL,
day integer NOT NULL,
color integer NOT NULL,
time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
metadata JSONB DEFAULT NULL
);
CREATE INDEX pixels_address_index ON Pixels (address);
CREATE INDEX pixels_position_index ON Pixels (position);
Expand Down
118 changes: 117 additions & 1 deletion backend/routes/indexer/pixel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package indexer

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/AFK-AlignedFamKernel/afk_monorepo/backend/core"
Expand Down Expand Up @@ -52,6 +54,7 @@ func processPixelPlacedEvent(event IndexerEvent) {
return
}

fmt.Printf(address, position, dayIdx, color, "print")
// Set pixel in postgres
_, err = core.AFKBackend.Databases.Postgres.Exec(context.Background(), "INSERT INTO Pixels (address, position, day, color) VALUES ($1, $2, $3, $4)", address, position, dayIdx, color)
if err != nil {
Expand Down Expand Up @@ -116,7 +119,6 @@ func revertPixelPlacedEvent(event IndexerEvent) {
func processBasicPixelPlacedEvent(event IndexerEvent) {
address := event.Event.Keys[1][2:] // Remove 0x prefix
timestampHex := event.Event.Data[0]

timestamp, err := strconv.ParseInt(timestampHex, 0, 64)
if err != nil {
PrintIndexerError("processBasicPixelPlacedEvent", "Error converting timestamp hex to int", address, timestampHex)
Expand Down Expand Up @@ -234,3 +236,117 @@ func revertExtraPixelsPlacedEvent(event IndexerEvent) {
return
}
}

func processBasicPixelPlacedEventWithMetadata(event IndexerEvent) {
address := event.Event.Keys[1][2:] // Remove 0x prefix
timestampHex := event.Event.Data[0]
timestamp, err := strconv.ParseInt(timestampHex, 0, 64)
if err != nil {
PrintIndexerError("processBasicPixelPlacedEventWithMetadata", "Error converting timestamp hex to int", address, timestampHex)
return
}

// Extract position and color from the event (position is Keys[2], color is in Data[1])
positionHex := event.Event.Keys[2]
position, err := strconv.Atoi(positionHex)
if err != nil {
PrintIndexerError("processBasicPixelPlacedEventWithMetadata", "Error converting position hex to int", address, positionHex)
return
}

colorHex := event.Event.Data[1]
color, err := strconv.Atoi(colorHex)
if err != nil {
PrintIndexerError("processBasicPixelPlacedEventWithMetadata", "Error converting color hex to int", address, colorHex)
return
}

// Extract metadata from the last index in Data (metadata is in Data[n])
metadata := event.Event.Data[len(event.Event.Data)-1]

// Unmarshal metadata (if it exists)
var metadataMap map[string]interface{}
if len(metadata) > 0 {
err = json.Unmarshal([]byte(metadata), &metadataMap)
if err != nil {
PrintIndexerError("processBasicPixelPlacedEventWithMetadata", "Error parsing metadata", address, string(metadata))
return
}
}

// Prepare SQL statement for inserting pixel info and metadata together
metadataJson, err := json.Marshal(metadataMap)
if err != nil {
PrintIndexerError("processBasicPixelPlacedEventWithMetadata", "Error serializing metadata", address, string(metadata))
return
}

// Use a single query to insert the pixel information and metadata into the database
_, err = core.AFKBackend.Databases.Postgres.Exec(context.Background(),
`INSERT INTO Pixels (address, position, color, time)
VALUES ($1, $2, $3, TO_TIMESTAMP($4))
ON CONFLICT (address, position)
DO UPDATE SET color = $3, time = TO_TIMESTAMP($4),
metadata = COALESCE(metadata, $5)`,
address, position, color, timestamp, metadataJson)
if err != nil {
PrintIndexerError("processBasicPixelPlacedEventWithMetadata", "Error inserting/updating pixel and metadata", address, string(metadataJson))
return
}

// Insert or update the last placed time in the LastPlacedTime table
_, err = core.AFKBackend.Databases.Postgres.Exec(context.Background(),
"INSERT INTO LastPlacedTime (address, time) VALUES ($1, TO_TIMESTAMP($2)) ON CONFLICT (address) DO UPDATE SET time = TO_TIMESTAMP($2)",
address, timestamp)
if err != nil {
PrintIndexerError("processBasicPixelPlacedEventWithMetadata", "Error inserting last placed time into postgres", address, timestampHex)
return
}
}

func revertBasicPixelPlacedEventWithMetadata(event IndexerEvent) {
address := event.Event.Keys[1][2:] // Remove 0x prefix
posHex := event.Event.Keys[2]

// Convert hex to int for position
position, err := strconv.ParseInt(posHex, 0, 64)
if err != nil {
PrintIndexerError("revertPixelPlacedEvent", "Error converting position hex to int", address, posHex)
return
}

// We can also retrieve the metadata from the event if needed
metadata := event.Event.Data[len(event.Event.Data)-1]
var metadataMap map[string]interface{}
if len(metadata) > 0 {
err = json.Unmarshal([]byte(metadata), &metadataMap) // Unmarshal from metadata (which is a string) to map
if err != nil {
PrintIndexerError("revertPixelPlacedEvent", "Error parsing metadata", address, string(metadata))
return
}
}

// Delete the pixel entry (including metadata) from the PostgreSQL database
_, err = core.AFKBackend.Databases.Postgres.Exec(context.Background(), `
DELETE FROM Pixels
WHERE address = $1 AND position = $2
ORDER BY time LIMIT 1`, address, position)
if err != nil {
PrintIndexerError("revertPixelPlacedEvent", "Error deleting pixel from postgres", address, posHex)
return
}

// Optionally, you can also delete the metadata from the database,
// but usually deleting the pixel entry will automatically take care of it since metadata is part of the same row.

// Delete the pixel's associated last placed time entry from the LastPlacedTime table
_, err = core.AFKBackend.Databases.Postgres.Exec(context.Background(),
"DELETE FROM LastPlacedTime WHERE address = $1", address)
if err != nil {
PrintIndexerError("revertPixelPlacedEvent", "Error deleting last placed time from postgres", address, posHex)
return
}

// Optionally log the event if needed
fmt.Printf("Pixel at position %d for address %s has been reverted.\n", position, address)
}
78 changes: 67 additions & 11 deletions backend/routes/pixel.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ package routes

import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"strconv"
"time"

"github.com/AFK-AlignedFamKernel/afk_monorepo/backend/core"
routeutils "github.com/AFK-AlignedFamKernel/afk_monorepo/backend/routes/utils"
)

// Define a struct to represent a Pixel record
type Pixel struct {
Address string `json:"address"`
Position int `json:"position"`
Day int `json:"day"`
Color int `json:"color"`
Time time.Time `json:"time"`
}

func InitPixelRoutes() {
http.HandleFunc("/get-pixel", getPixel)
http.HandleFunc("/get-pixel-info", getPixelInfo)
Expand Down Expand Up @@ -53,32 +64,78 @@ func getPixel(w http.ResponseWriter, r *http.Request) {
}

type PixelInfo struct {
Address string `json:"address"`
Name string `json:"username"`
Address string `json:"address"`
Name string `json:"username"`
Metadata json.RawMessage `json:"metadata,omitempty"`
}

// func getPixelInfo(w http.ResponseWriter, r *http.Request) {
// position, err := strconv.Atoi(r.URL.Query().Get("position"))
// if err != nil {
// routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid query position")
// return
// }

// queryRes, err := core.PostgresQueryOne[PixelInfo](`
// SELECT p.address, COALESCE(u.name, '') as name FROM Pixels p
// LEFT JOIN Users u ON p.address = u.address WHERE p.position = $1
// ORDER BY p.time DESC LIMIT 1`, position)
// if err != nil {
// routeutils.WriteDataJson(w, "\"0x0000000000000000000000000000000000000000000000000000000000000000\"")
// return
// }

// if queryRes.Name == "" {
// routeutils.WriteDataJson(w, "\"0x"+queryRes.Address+"\"")
// } else {
// routeutils.WriteDataJson(w, "\""+queryRes.Name+"\"")
// }
// }

// New implmentation with metadata
func getPixelInfo(w http.ResponseWriter, r *http.Request) {
position, err := strconv.Atoi(r.URL.Query().Get("position"))
if err != nil {
routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid query position")
return
}

// Update the query to include metadata
queryRes, err := core.PostgresQueryOne[PixelInfo](`
SELECT p.address, COALESCE(u.name, '') as name FROM Pixels p
LEFT JOIN Users u ON p.address = u.address WHERE p.position = $1
ORDER BY p.time DESC LIMIT 1`, position)
SELECT
p.address,
COALESCE(u.name, '') as name,
p.metadata -- Fetch the metadata column as well
FROM Pixels p
LEFT JOIN Users u ON p.address = u.address
WHERE p.position = $1
ORDER BY p.time DESC
LIMIT 1`, position)
if err != nil {
routeutils.WriteDataJson(w, "\"0x0000000000000000000000000000000000000000000000000000000000000000\"")
return
}

// If queryRes.Name is empty, return the address, else return the name
if queryRes.Name == "" {
routeutils.WriteDataJson(w, "\"0x"+queryRes.Address+"\"")
response := "\"0x" + queryRes.Address + "\""
if queryRes.Metadata != nil {
// If metadata exists, include it in the response
metadataJson, _ := json.Marshal(queryRes.Metadata)
response = fmt.Sprintf("{\"address\": \"%s\", \"metadata\": %s}", queryRes.Address, string(metadataJson))
}
routeutils.WriteDataJson(w, response)
} else {
routeutils.WriteDataJson(w, "\""+queryRes.Name+"\"")
response := "\"" + queryRes.Name + "\""
if queryRes.Metadata != nil {
// If metadata exists, include it in the response
metadataJson, _ := json.Marshal(queryRes.Metadata)
response = fmt.Sprintf("{\"name\": \"%s\", \"metadata\": %s}", queryRes.Name, string(metadataJson))
}
routeutils.WriteDataJson(w, response)
}
}

func getPixelMetadata(w http.ResponseWriter, r *http.Request) {
position, err := strconv.Atoi(r.URL.Query().Get("position"))
if err != nil {
Expand All @@ -102,7 +159,6 @@ func getPixelMetadata(w http.ResponseWriter, r *http.Request) {
}
}


func placePixelDevnet(w http.ResponseWriter, r *http.Request) {
// Disable this in production
if routeutils.NonProductionMiddleware(w, r) {
Expand Down Expand Up @@ -208,7 +264,7 @@ func placePixelRedis(w http.ResponseWriter, r *http.Request) {
}

jsonBody, err := routeutils.ReadJsonBody[map[string]uint](r)
fmt.Println("jsonBody", jsonBody)
fmt.Println("jsonBody", r.Body)

if err != nil {
routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid JSON request body")
Expand All @@ -218,6 +274,8 @@ func placePixelRedis(w http.ResponseWriter, r *http.Request) {
position := (*jsonBody)["position"]
color := (*jsonBody)["color"]

fmt.Println("jsonBody", position)

canvasWidth := core.AFKBackend.CanvasConfig.Canvas.Width
canvasHeight := core.AFKBackend.CanvasConfig.Canvas.Height

Expand Down Expand Up @@ -250,7 +308,5 @@ func placePixelRedis(w http.ResponseWriter, r *http.Request) {
return
}

fmt.Println("Error redis insert pixel", err.Error())

routeutils.WriteResultJson(w, "Pixel placed on redis")
}
Loading

0 comments on commit e914250

Please sign in to comment.