diff --git a/backend/config/backend.go b/backend/config/backend.go index a7de7734..573b143b 100644 --- a/backend/config/backend.go +++ b/backend/config/backend.go @@ -6,20 +6,22 @@ import ( ) type BackendScriptsConfig struct { - PlacePixelDevnet string `json:"place_pixel_devnet"` - PlaceExtraPixelsDevnet string `json:"place_extra_pixels_devnet"` - AddTemplateDevnet string `json:"add_template_devnet"` - ClaimTodayQuestDevnet string `json:"claim_today_quest_devnet"` - MintNFTDevnet string `json:"mint_nft_devnet"` - LikeNFTDevnet string `json:"like_nft_devnet"` - UnlikeNFTDevnet string `json:"unlike_nft_devnet"` - VoteColorDevnet string `json:"vote_color_devnet"` - NewUsernameDevnet string `json:"new_username_devnet"` - ChangeUsernameDevnet string `json:"change_username_devnet"` - IncreaseDayDevnet string `json:"increase_day_devnet"` - JoinChainFactionDevnet string `json:"join_chain_faction_devnet"` - JoinFactionDevnet string `json:"join_faction_devnet"` - LeaveFactionDevnet string `json:"leave_faction_devnet"` + PlacePixelDevnet string `json:"place_pixel_devnet"` + PlaceExtraPixelsDevnet string `json:"place_extra_pixels_devnet"` + AddTemplateDevnet string `json:"add_template_devnet"` + ClaimTodayQuestDevnet string `json:"claim_today_quest_devnet"` + MintNFTDevnet string `json:"mint_nft_devnet"` + LikeNFTDevnet string `json:"like_nft_devnet"` + UnlikeNFTDevnet string `json:"unlike_nft_devnet"` + VoteColorDevnet string `json:"vote_color_devnet"` + NewUsernameDevnet string `json:"new_username_devnet"` + ChangeUsernameDevnet string `json:"change_username_devnet"` + IncreaseDayDevnet string `json:"increase_day_devnet"` + JoinChainFactionDevnet string `json:"join_chain_faction_devnet"` + JoinFactionDevnet string `json:"join_faction_devnet"` + LeaveFactionDevnet string `json:"leave_faction_devnet"` + AddFactionTemplateDevnet string `json:"add_faction_template_devnet"` + RemoveFactionTemplateDevnet string `json:"remove_faction_template_devnet"` } type WebSocketConfig struct { @@ -48,20 +50,22 @@ var DefaultBackendConfig = BackendConfig{ Port: 8080, ConsumerPort: 8081, Scripts: BackendScriptsConfig{ - PlacePixelDevnet: "../scripts/place_pixel.sh", - PlaceExtraPixelsDevnet: "../scripts/place_extra_pixels.sh", - AddTemplateDevnet: "../scripts/add_template.sh", - ClaimTodayQuestDevnet: "../scripts/claim_today_quest.sh", - MintNFTDevnet: "../scripts/mint_nft.sh", - LikeNFTDevnet: "../scripts/like_nft.sh", - UnlikeNFTDevnet: "../scripts/unlike_nft.sh", - VoteColorDevnet: "../scripts/vote_color.sh", - NewUsernameDevnet: "../scripts/new_username.sh", - ChangeUsernameDevnet: "../scripts/change_username.sh", - IncreaseDayDevnet: "../scripts/increase_day_index.sh", - JoinChainFactionDevnet: "../scripts/join_chain_faction.sh", - JoinFactionDevnet: "../scripts/join_faction.sh", - LeaveFactionDevnet: "../scripts/leave_faction.sh", + PlacePixelDevnet: "../scripts/place_pixel.sh", + PlaceExtraPixelsDevnet: "../scripts/place_extra_pixels.sh", + AddTemplateDevnet: "../scripts/add_template.sh", + ClaimTodayQuestDevnet: "../scripts/claim_today_quest.sh", + MintNFTDevnet: "../scripts/mint_nft.sh", + LikeNFTDevnet: "../scripts/like_nft.sh", + UnlikeNFTDevnet: "../scripts/unlike_nft.sh", + VoteColorDevnet: "../scripts/vote_color.sh", + NewUsernameDevnet: "../scripts/new_username.sh", + ChangeUsernameDevnet: "../scripts/change_username.sh", + IncreaseDayDevnet: "../scripts/increase_day_index.sh", + JoinChainFactionDevnet: "../scripts/join_chain_faction.sh", + JoinFactionDevnet: "../scripts/join_faction.sh", + LeaveFactionDevnet: "../scripts/leave_faction.sh", + AddFactionTemplateDevnet: "../scripts/add_faction_template.sh", + RemoveFactionTemplateDevnet: "../scripts/remove_faction_template.sh", }, Production: false, WebSocket: WebSocketConfig{ diff --git a/backend/core/backend.go b/backend/core/backend.go index 93845af8..53a038e3 100644 --- a/backend/core/backend.go +++ b/backend/core/backend.go @@ -3,6 +3,7 @@ package core import ( "fmt" "net/http" + "sync" "github.com/gorilla/websocket" @@ -12,7 +13,8 @@ import ( type Backend struct { Databases *Databases // TODO: Is this thread safe? - WSConnections []*websocket.Conn + WSConnections []*websocket.Conn + WSConnectionsLock sync.Mutex CanvasConfig *config.CanvasConfig BackendConfig *config.BackendConfig diff --git a/backend/routes/contract.go b/backend/routes/contract.go index 3affbe27..8b50cd8b 100644 --- a/backend/routes/contract.go +++ b/backend/routes/contract.go @@ -39,8 +39,9 @@ func setContractAddress(w http.ResponseWriter, r *http.Request) { } type GameData struct { - Day int `json:"day"` - EndTime int `json:"endTime"` + Day int `json:"day"` + EndTime int `json:"endTime"` + Host string `json:"host"` } func getGameData(w http.ResponseWriter, r *http.Request) { @@ -61,9 +62,16 @@ func getGameData(w http.ResponseWriter, r *http.Request) { return } + host := os.Getenv("ART_PEACE_HOST") + if host == "" { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get host") + return + } + gameData := GameData{ Day: *day, EndTime: endTimeInt, + Host: host, } jsonGameData, err := json.Marshal(gameData) if err != nil { diff --git a/backend/routes/factions.go b/backend/routes/factions.go index 7aee683a..3857be2f 100644 --- a/backend/routes/factions.go +++ b/backend/routes/factions.go @@ -35,6 +35,7 @@ type FactionUserData struct { FactionId int `json:"factionId"` Allocation int `json:"allocation"` Name string `json:"name"` + Leader string `json:"leader"` Members int `json:"members"` Joinable bool `json:"joinable"` Icon string `json:"icon"` @@ -47,6 +48,7 @@ type FactionUserData struct { type FactionData struct { FactionId int `json:"factionId"` Name string `json:"name"` + Leader string `json:"leader"` Members int `json:"members"` IsMember bool `json:"isMember"` Joinable bool `json:"joinable"` @@ -165,7 +167,7 @@ func getMyFactions(w http.ResponseWriter, r *http.Request) { // TODO: Paginate and accumulate the allocations for each faction query := ` - SELECT m.faction_id, f.allocation, f.name, COALESCE((SELECT COUNT(*) FROM factionmembersinfo WHERE faction_id = m.faction_id), 0) as members, f.joinable, COALESCE(icon, '') as icon, COALESCE(telegram, '') as telegram, COALESCE(twitter, '') as twitter, COALESCE(github, '') as github, COALESCE(site, '') as site + SELECT m.faction_id, f.allocation, f.name, f.leader, COALESCE((SELECT COUNT(*) FROM factionmembersinfo WHERE faction_id = m.faction_id), 0) as members, f.joinable, COALESCE(icon, '') as icon, COALESCE(telegram, '') as telegram, COALESCE(twitter, '') as twitter, COALESCE(github, '') as github, COALESCE(site, '') as site FROM factionmembersinfo m LEFT JOIN factions f ON m.faction_id = f.faction_id LEFT JOIN FactionLinks l ON m.faction_id = l.faction_id @@ -200,7 +202,7 @@ func getFactions(w http.ResponseWriter, r *http.Request) { offset := (page - 1) * pageLength query := ` - SELECT f.faction_id, name, COALESCE((SELECT COUNT(*) FROM factionmembersinfo fm WHERE f.faction_id = fm.faction_id), 0) as members, + SELECT f.faction_id, name, leader, COALESCE((SELECT COUNT(*) FROM factionmembersinfo fm WHERE f.faction_id = fm.faction_id), 0) as members, COALESCE((SELECT COUNT(*) FROM factionmembersinfo fm WHERE f.faction_id = fm.faction_id AND user_address = $1), 0) > 0 as is_member, f.joinable, COALESCE(icon, '') as icon, COALESCE(telegram, '') as telegram, COALESCE(twitter, '') as twitter, COALESCE(github, '') as github, COALESCE(site, '') as site FROM factions f @@ -224,7 +226,7 @@ func getMyChainFactions(w http.ResponseWriter, r *http.Request) { } query := ` - SELECT f.faction_id, name, COALESCE((SELECT COUNT(*) FROM chainfactionmembersinfo fm WHERE f.faction_id = fm.faction_id), 0) as members, + SELECT f.faction_id, name, 'N/A' as leader, COALESCE((SELECT COUNT(*) FROM chainfactionmembersinfo fm WHERE f.faction_id = fm.faction_id), 0) as members, COALESCE((SELECT COUNT(*) FROM chainfactionmembersinfo fm WHERE f.faction_id = fm.faction_id AND user_address = $1), 0) > 0 as is_member, true as joinable, COALESCE(icon, '') as icon, COALESCE(telegram, '') as telegram, COALESCE(twitter, '') as twitter, COALESCE(github, '') as github, COALESCE(site, '') as site FROM chainfactionmembersinfo m @@ -249,7 +251,7 @@ func getChainFactions(w http.ResponseWriter, r *http.Request) { } query := ` - SELECT f.faction_id, name, COALESCE((SELECT COUNT(*) FROM chainfactionmembersinfo fm WHERE f.faction_id = fm.faction_id), 0) as members, + SELECT f.faction_id, name, 'N/A' as leader, COALESCE((SELECT COUNT(*) FROM chainfactionmembersinfo fm WHERE f.faction_id = fm.faction_id), 0) as members, COALESCE((SELECT COUNT(*) FROM chainfactionmembersinfo fm WHERE f.faction_id = fm.faction_id AND user_address = $1), 0) > 0 as is_member, true as joinable, COALESCE(icon, '') as icon, COALESCE(telegram, '') as telegram, COALESCE(twitter, '') as twitter, COALESCE(github, '') as github, COALESCE(site, '') as site FROM ChainFactions f @@ -368,6 +370,7 @@ func joinChainFactionDevnet(w http.ResponseWriter, r *http.Request) { return } + // TODO : 64 characters is the max length for a chainId if len(chainId) > 31 { routeutils.WriteErrorJson(w, http.StatusBadRequest, "chainId too long (max 31 characters)") return diff --git a/backend/routes/indexer/faction.go b/backend/routes/indexer/faction.go index def5723f..054f8ba6 100644 --- a/backend/routes/indexer/faction.go +++ b/backend/routes/indexer/faction.go @@ -75,6 +75,27 @@ func revertFactionCreatedEvent(event IndexerEvent) { } } +func processFactionLeaderChangedEvent(event IndexerEvent) { + factionIdHex := event.Event.Keys[1] + newLeader := event.Event.Data[0][2:] // Remove 0x prefix + + factionId, err := strconv.ParseInt(factionIdHex, 0, 64) + if err != nil { + PrintIndexerError("processFactionLeaderChangedEvent", "Failed to parse factionId", factionIdHex, newLeader) + return + } + + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "UPDATE Factions SET leader = $1 WHERE faction_id = $2", newLeader, factionId) + if err != nil { + PrintIndexerError("processFactionLeaderChangedEvent", "Failed to update faction leader in postgres", factionIdHex, newLeader) + return + } +} + +func revertFactionLeaderChangedEvent(event IndexerEvent) { + // TODO: Implement +} + func processFactionJoinedEvent(event IndexerEvent) { factionIdHex := event.Event.Keys[1] userAddress := event.Event.Keys[2][2:] // Remove 0x prefix diff --git a/backend/routes/indexer/route.go b/backend/routes/indexer/route.go index 1aa24257..3687b1f0 100644 --- a/backend/routes/indexer/route.go +++ b/backend/routes/indexer/route.go @@ -54,107 +54,123 @@ var FinalizedMessageQueue []IndexerMessage var FinalizedMessageLock = &sync.Mutex{} const ( - newDayEvent = "0x00df776faf675d0c64b0f2ec596411cf1509d3966baba3478c84771ddbac1784" - colorAddedEvent = "0x0004a301e4d01f413a1d4d0460c4ba976e23392f49126d90f5bd45de7dd7dbeb" - pixelPlacedEvent = "0x02d7b50ebf415606d77c7e7842546fc13f8acfbfd16f7bcf2bc2d08f54114c23" - basicPixelPlacedEvent = "0x03089ae3085e1c52442bb171f26f92624095d32dc8a9c57c8fb09130d32daed8" - factionPixelsPlacedEvent = "0x02838056c6784086957f2252d4a36a24d554ea2db7e09d2806cc69751d81f0a2" - chainFactionPixelsPlacedEvent = "0x02e4d1feaacd0627a6c7d5002564bdb4ca4877d47f00cad4714201194690a7a9" - extraPixelsPlacedEvent = "0x000e8f5c4e6f651bf4c7b093805f85c9b8ec2ec428210f90a4c9c135c347f48c" - dailyQuestClaimedEvent = "0x02025eddbc0f68a923d76519fb336e0fe1e0d6b9053ab3a504251bbd44201b10" - mainQuestClaimedEvent = "0x0121172d5bc3847c8c39069075125e53d3225741d190df6d52194cb5dd5d2049" - voteColorEvent = "0x02407c82b0efa2f6176a075ba5a939d33eefab39895fabcf3ac1c5e897974a40" - votableColorAddedEvent = "0x0115b3bc605487276e022f4bec68b316e7a6b3615fb01afee58241fd1d40e3e5" - factionCreatedEvent = "0x00f3878d4c85ed94271bb611f83d47ea473bae501ffed34cd21b73206149f692" - factionJoinedEvent = "0x01e3fbdf8156ad0dde21e886d61a16d85c9ef54451eb6e253f3f427de32a47ac" - factionLeftEvent = "0x014ef8cc25c96157e2a00e9ceaa7c014a162d11d58a98871087ec488a67d7925" - chainFactionCreatedEvent = "0x020c994ab49a8316bcc78b06d4ff9929d83b2995af33f480b93e972cedb0c926" - chainFactionJoinedEvent = "0x02947960ff713d9b594a3b718b90a45360e46d1bbacef94b727bb0d461d04207" - nftMintedEvent = "0x030826e0cd9a517f76e857e3f3100fe5b9098e9f8216d3db283fb4c9a641232f" - nftLikedEvent = "0x028d7ee09447088eecdd12a86c9467a5e9ad18f819a20f9adcf6e34e0bd51453" - nftUnlikedEvent = "0x03b57514b19693484c35249c6e8b15bfe6e476205720680c2ff9f02faaf94941" - usernameClaimedEvent = "0x019be6537c04b790ae4e3a06d6e777ec8b2e9950a01d76eed8a2a28941cc511c" - usernameChangedEvent = "0x03c44b98666b0a27eadcdf5dc42449af5f907b19523858368c4ffbc7a2625dab" - templateAddedEvent = "0x03e18ec266fe76a2efce73f91228e6e04456b744fc6984c7a6374e417fb4bf59" - nftTransferEvent = "0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" + newDayEvent = "0x00df776faf675d0c64b0f2ec596411cf1509d3966baba3478c84771ddbac1784" + colorAddedEvent = "0x0004a301e4d01f413a1d4d0460c4ba976e23392f49126d90f5bd45de7dd7dbeb" + pixelPlacedEvent = "0x02d7b50ebf415606d77c7e7842546fc13f8acfbfd16f7bcf2bc2d08f54114c23" + basicPixelPlacedEvent = "0x03089ae3085e1c52442bb171f26f92624095d32dc8a9c57c8fb09130d32daed8" + factionPixelsPlacedEvent = "0x02838056c6784086957f2252d4a36a24d554ea2db7e09d2806cc69751d81f0a2" + chainFactionPixelsPlacedEvent = "0x02e4d1feaacd0627a6c7d5002564bdb4ca4877d47f00cad4714201194690a7a9" + extraPixelsPlacedEvent = "0x000e8f5c4e6f651bf4c7b093805f85c9b8ec2ec428210f90a4c9c135c347f48c" + dailyQuestClaimedEvent = "0x02025eddbc0f68a923d76519fb336e0fe1e0d6b9053ab3a504251bbd44201b10" + mainQuestClaimedEvent = "0x0121172d5bc3847c8c39069075125e53d3225741d190df6d52194cb5dd5d2049" + voteColorEvent = "0x02407c82b0efa2f6176a075ba5a939d33eefab39895fabcf3ac1c5e897974a40" + votableColorAddedEvent = "0x0115b3bc605487276e022f4bec68b316e7a6b3615fb01afee58241fd1d40e3e5" + factionCreatedEvent = "0x00f3878d4c85ed94271bb611f83d47ea473bae501ffed34cd21b73206149f692" + factionLeaderChangedEvent = "0x00aa4bacdfcf2717835a46fbd64f7d39bfdf2b4404bc5af8e5660415d1dc2848" + factionJoinedEvent = "0x01e3fbdf8156ad0dde21e886d61a16d85c9ef54451eb6e253f3f427de32a47ac" + factionLeftEvent = "0x014ef8cc25c96157e2a00e9ceaa7c014a162d11d58a98871087ec488a67d7925" + chainFactionCreatedEvent = "0x020c994ab49a8316bcc78b06d4ff9929d83b2995af33f480b93e972cedb0c926" + chainFactionJoinedEvent = "0x02947960ff713d9b594a3b718b90a45360e46d1bbacef94b727bb0d461d04207" + nftMintedEvent = "0x030826e0cd9a517f76e857e3f3100fe5b9098e9f8216d3db283fb4c9a641232f" + nftLikedEvent = "0x028d7ee09447088eecdd12a86c9467a5e9ad18f819a20f9adcf6e34e0bd51453" + nftUnlikedEvent = "0x03b57514b19693484c35249c6e8b15bfe6e476205720680c2ff9f02faaf94941" + usernameClaimedEvent = "0x019be6537c04b790ae4e3a06d6e777ec8b2e9950a01d76eed8a2a28941cc511c" + usernameChangedEvent = "0x03c44b98666b0a27eadcdf5dc42449af5f907b19523858368c4ffbc7a2625dab" + nftTransferEvent = "0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9" + factionTemplateAddedEvent = "0x026ab80224b4bc3543bf20cd8b66304b3591c05eac775d823e1970514881757f" + factionTemplateRemovedEvent = "0x029a976c0074fc910f3a6a58f1351c48dab7b1c539f54ed930616292c806283f" + chainFactionTemplateAddedEvent = "0x00476f35ea27024c89c1fc05dfad873e9e93419e452ee781e8207e435289a39b" + chainFactionTemplateRemovedEvent = "0x0126718de7cb8b83dfa258eb095bc0ec7a3ef5a2258ebd1ed349551764856c6b" ) var eventProcessors = map[string](func(IndexerEvent)){ - newDayEvent: processNewDayEvent, - colorAddedEvent: processColorAddedEvent, - pixelPlacedEvent: processPixelPlacedEvent, - basicPixelPlacedEvent: processBasicPixelPlacedEvent, - factionPixelsPlacedEvent: processFactionPixelsPlacedEvent, - chainFactionPixelsPlacedEvent: processChainFactionPixelsPlacedEvent, - extraPixelsPlacedEvent: processExtraPixelsPlacedEvent, - dailyQuestClaimedEvent: processDailyQuestClaimedEvent, - mainQuestClaimedEvent: processMainQuestClaimedEvent, - voteColorEvent: processVoteColorEvent, - votableColorAddedEvent: processVotableColorAddedEvent, - factionCreatedEvent: processFactionCreatedEvent, - factionJoinedEvent: processFactionJoinedEvent, - factionLeftEvent: processFactionLeftEvent, - chainFactionCreatedEvent: processChainFactionCreatedEvent, - chainFactionJoinedEvent: processChainFactionJoinedEvent, - nftMintedEvent: processNFTMintedEvent, - nftLikedEvent: processNFTLikedEvent, - nftUnlikedEvent: processNFTUnlikedEvent, - usernameClaimedEvent: processUsernameClaimedEvent, - usernameChangedEvent: processUsernameChangedEvent, - templateAddedEvent: processTemplateAddedEvent, - nftTransferEvent: processNFTTransferEvent, + newDayEvent: processNewDayEvent, + colorAddedEvent: processColorAddedEvent, + pixelPlacedEvent: processPixelPlacedEvent, + basicPixelPlacedEvent: processBasicPixelPlacedEvent, + factionPixelsPlacedEvent: processFactionPixelsPlacedEvent, + chainFactionPixelsPlacedEvent: processChainFactionPixelsPlacedEvent, + extraPixelsPlacedEvent: processExtraPixelsPlacedEvent, + dailyQuestClaimedEvent: processDailyQuestClaimedEvent, + mainQuestClaimedEvent: processMainQuestClaimedEvent, + voteColorEvent: processVoteColorEvent, + votableColorAddedEvent: processVotableColorAddedEvent, + factionCreatedEvent: processFactionCreatedEvent, + factionLeaderChangedEvent: processFactionLeaderChangedEvent, + factionJoinedEvent: processFactionJoinedEvent, + factionLeftEvent: processFactionLeftEvent, + chainFactionCreatedEvent: processChainFactionCreatedEvent, + chainFactionJoinedEvent: processChainFactionJoinedEvent, + nftMintedEvent: processNFTMintedEvent, + nftLikedEvent: processNFTLikedEvent, + nftUnlikedEvent: processNFTUnlikedEvent, + usernameClaimedEvent: processUsernameClaimedEvent, + usernameChangedEvent: processUsernameChangedEvent, + nftTransferEvent: processNFTTransferEvent, + factionTemplateAddedEvent: processFactionTemplateAddedEvent, + factionTemplateRemovedEvent: processFactionTemplateRemovedEvent, + chainFactionTemplateAddedEvent: processChainFactionTemplateAddedEvent, + chainFactionTemplateRemovedEvent: processChainFactionTemplateRemovedEvent, } var eventReverters = map[string](func(IndexerEvent)){ - newDayEvent: revertNewDayEvent, - colorAddedEvent: revertColorAddedEvent, - pixelPlacedEvent: revertPixelPlacedEvent, - basicPixelPlacedEvent: revertBasicPixelPlacedEvent, - factionPixelsPlacedEvent: revertFactionPixelsPlacedEvent, - chainFactionPixelsPlacedEvent: revertChainFactionPixelsPlacedEvent, - extraPixelsPlacedEvent: revertExtraPixelsPlacedEvent, - dailyQuestClaimedEvent: revertDailyQuestClaimedEvent, - mainQuestClaimedEvent: revertMainQuestClaimedEvent, - voteColorEvent: revertVoteColorEvent, - votableColorAddedEvent: revertVotableColorAddedEvent, - factionCreatedEvent: revertFactionCreatedEvent, - factionJoinedEvent: revertFactionJoinedEvent, - factionLeftEvent: revertFactionLeftEvent, - chainFactionCreatedEvent: revertChainFactionCreatedEvent, - chainFactionJoinedEvent: revertChainFactionJoinedEvent, - nftMintedEvent: revertNFTMintedEvent, - nftLikedEvent: revertNFTLikedEvent, - nftUnlikedEvent: revertNFTUnlikedEvent, - usernameClaimedEvent: revertUsernameClaimedEvent, - usernameChangedEvent: revertUsernameChangedEvent, - templateAddedEvent: revertTemplateAddedEvent, - nftTransferEvent: revertNFTTransferEvent, + newDayEvent: revertNewDayEvent, + colorAddedEvent: revertColorAddedEvent, + pixelPlacedEvent: revertPixelPlacedEvent, + basicPixelPlacedEvent: revertBasicPixelPlacedEvent, + factionPixelsPlacedEvent: revertFactionPixelsPlacedEvent, + chainFactionPixelsPlacedEvent: revertChainFactionPixelsPlacedEvent, + extraPixelsPlacedEvent: revertExtraPixelsPlacedEvent, + dailyQuestClaimedEvent: revertDailyQuestClaimedEvent, + mainQuestClaimedEvent: revertMainQuestClaimedEvent, + voteColorEvent: revertVoteColorEvent, + votableColorAddedEvent: revertVotableColorAddedEvent, + factionCreatedEvent: revertFactionCreatedEvent, + factionLeaderChangedEvent: revertFactionLeaderChangedEvent, + factionJoinedEvent: revertFactionJoinedEvent, + factionLeftEvent: revertFactionLeftEvent, + chainFactionCreatedEvent: revertChainFactionCreatedEvent, + chainFactionJoinedEvent: revertChainFactionJoinedEvent, + nftMintedEvent: revertNFTMintedEvent, + nftLikedEvent: revertNFTLikedEvent, + nftUnlikedEvent: revertNFTUnlikedEvent, + usernameClaimedEvent: revertUsernameClaimedEvent, + usernameChangedEvent: revertUsernameChangedEvent, + nftTransferEvent: revertNFTTransferEvent, + factionTemplateAddedEvent: revertFactionTemplateAddedEvent, + factionTemplateRemovedEvent: revertFactionTemplateRemovedEvent, + chainFactionTemplateAddedEvent: revertChainFactionTemplateAddedEvent, + chainFactionTemplateRemovedEvent: revertChainFactionTemplateRemovedEvent, } var eventRequiresOrdering = map[string]bool{ - newDayEvent: false, - colorAddedEvent: true, - pixelPlacedEvent: true, - basicPixelPlacedEvent: false, - factionPixelsPlacedEvent: false, - chainFactionPixelsPlacedEvent: false, - extraPixelsPlacedEvent: false, - dailyQuestClaimedEvent: false, - mainQuestClaimedEvent: false, - voteColorEvent: true, - votableColorAddedEvent: true, - factionCreatedEvent: true, - factionJoinedEvent: true, - factionLeftEvent: true, - chainFactionCreatedEvent: true, - chainFactionJoinedEvent: true, - nftMintedEvent: false, - nftLikedEvent: true, - nftUnlikedEvent: true, - usernameClaimedEvent: false, - usernameChangedEvent: true, - templateAddedEvent: false, - nftTransferEvent: true, + newDayEvent: false, + colorAddedEvent: true, + pixelPlacedEvent: true, + basicPixelPlacedEvent: false, + factionPixelsPlacedEvent: false, + chainFactionPixelsPlacedEvent: false, + extraPixelsPlacedEvent: false, + dailyQuestClaimedEvent: false, + mainQuestClaimedEvent: false, + voteColorEvent: true, + votableColorAddedEvent: true, + factionCreatedEvent: true, + factionLeaderChangedEvent: true, + factionJoinedEvent: true, + factionLeftEvent: true, + chainFactionCreatedEvent: true, + chainFactionJoinedEvent: true, + nftMintedEvent: false, + nftLikedEvent: true, + nftUnlikedEvent: true, + usernameClaimedEvent: false, + usernameChangedEvent: true, + nftTransferEvent: true, + factionTemplateAddedEvent: true, + factionTemplateRemovedEvent: true, + chainFactionTemplateAddedEvent: true, + chainFactionTemplateRemovedEvent: true, } const ( diff --git a/backend/routes/indexer/template.go b/backend/routes/indexer/template.go index 4ccf7f03..17fc7cd7 100644 --- a/backend/routes/indexer/template.go +++ b/backend/routes/indexer/template.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "strconv" + "strings" "github.com/keep-starknet-strange/art-peace/backend/core" ) @@ -94,3 +95,201 @@ func revertTemplateAddedEvent(event IndexerEvent) { return } } + +func processFactionTemplateAddedEvent(event IndexerEvent) { + templateIdHex := event.Event.Keys[1] + factionIdHex := event.Event.Data[0] + factionTemplateHashHex := event.Event.Data[1][2:] // Remove 0x prefix + factionTemplatePositionHex := event.Event.Data[2] + factionTemplateWidthHex := event.Event.Data[3] + factionTemplateHeightHex := event.Event.Data[4] + + templateId, err := strconv.ParseInt(templateIdHex, 0, 64) + if err != nil { + PrintIndexerError("processFactionTemplateAddedEvent", "Error converting template id hex to int", templateIdHex, factionIdHex, factionTemplateHashHex, factionTemplatePositionHex, factionTemplateWidthHex, factionTemplateHeightHex) + return + } + + factionId, err := strconv.ParseInt(factionIdHex, 0, 64) + if err != nil { + PrintIndexerError("processFactionTemplateAddedEvent", "Error converting faction id hex to int", templateIdHex, factionIdHex, factionTemplateHashHex, factionTemplatePositionHex, factionTemplateWidthHex, factionTemplateHeightHex) + return + } + + imageHashLowercase := strings.ToLower(factionTemplateHashHex) + + factionTemplatePosition, err := strconv.ParseInt(factionTemplatePositionHex, 0, 64) + if err != nil { + PrintIndexerError("processFactionTemplateAddedEvent", "Error converting faction template position hex to int", templateIdHex, factionIdHex, factionTemplateHashHex, factionTemplatePositionHex, factionTemplateWidthHex, factionTemplateHeightHex) + return + } + + factionTemplateWidth, err := strconv.ParseInt(factionTemplateWidthHex, 0, 64) + if err != nil { + PrintIndexerError("processFactionTemplateAddedEvent", "Error converting faction template width hex to int", templateIdHex, factionIdHex, factionTemplateHashHex, factionTemplatePositionHex, factionTemplateWidthHex, factionTemplateHeightHex) + return + } + + factionTemplateHeight, err := strconv.ParseInt(factionTemplateHeightHex, 0, 64) + if err != nil { + PrintIndexerError("processFactionTemplateAddedEvent", "Error converting faction template height hex to int", templateIdHex, factionIdHex, factionTemplateHashHex, factionTemplatePositionHex, factionTemplateWidthHex, factionTemplateHeightHex) + return + } + + // Add faction template to postgres + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "INSERT INTO FactionTemplates (template_id, faction_id, hash, position, width, height, stale) VALUES ($1, $2, $3, $4, $5, $6, $7)", templateId, factionId, imageHashLowercase, factionTemplatePosition, factionTemplateWidth, factionTemplateHeight, false) + if err != nil { + PrintIndexerError("processFactionTemplateAddedEvent", "Error inserting faction template into postgres", templateIdHex, factionIdHex, factionTemplateHashHex, factionTemplatePositionHex, factionTemplateWidthHex, factionTemplateHeightHex) + return + } +} + +func revertFactionTemplateAddedEvent(event IndexerEvent) { + templateIdHex := event.Event.Keys[1] + + templateId, err := strconv.ParseInt(templateIdHex, 0, 64) + if err != nil { + PrintIndexerError("reverseFactionTemplateAddedEvent", "Error converting template id hex to int", templateIdHex) + return + } + + // Remove faction template from postgres + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "DELETE FROM FactionTemplates WHERE template_id = $1", templateId) + if err != nil { + PrintIndexerError("reverseFactionTemplateAddedEvent", "Error deleting faction template from postgres", templateIdHex) + return + } +} + +func processFactionTemplateRemovedEvent(event IndexerEvent) { + templateIdHex := event.Event.Keys[1] + + templateId, err := strconv.ParseInt(templateIdHex, 0, 64) + if err != nil { + PrintIndexerError("processFactionTemplateRemovedEvent", "Error converting template id hex to int", templateIdHex) + return + } + + // Mark faction template as stale in postgres + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "UPDATE FactionTemplates SET stale = true WHERE template_id = $1", templateId) + if err != nil { + PrintIndexerError("processFactionTemplateRemovedEvent", "Error marking faction template as stale in postgres", templateIdHex) + return + } +} + +func revertFactionTemplateRemovedEvent(event IndexerEvent) { + templateIdHex := event.Event.Keys[1] + + templateId, err := strconv.ParseInt(templateIdHex, 0, 64) + if err != nil { + PrintIndexerError("reverseFactionTemplateRemovedEvent", "Error converting template id hex to int", templateIdHex) + return + } + + // Unmark faction template as stale in postgres + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "UPDATE FactionTemplates SET stale = false WHERE template_id = $1", templateId) + if err != nil { + PrintIndexerError("reverseFactionTemplateRemovedEvent", "Error unmarking faction template as stale in postgres", templateIdHex) + return + } +} + +func processChainFactionTemplateAddedEvent(event IndexerEvent) { + templateIdHex := event.Event.Keys[1] + chainIdHex := event.Event.Data[0] + chainTemplateHashHex := event.Event.Data[1][2:] // Remove 0x prefix + chainTemplatePositionHex := event.Event.Data[2] + chainTemplateWidthHex := event.Event.Data[3] + chainTemplateHeightHex := event.Event.Data[4] + + templateId, err := strconv.ParseInt(templateIdHex, 0, 64) + if err != nil { + PrintIndexerError("processChainTemplateAddedEvent", "Error converting template id hex to int", templateIdHex, chainIdHex, chainTemplateHashHex, chainTemplatePositionHex, chainTemplateWidthHex, chainTemplateHeightHex) + return + } + + chainId, err := strconv.ParseInt(chainIdHex, 0, 64) + if err != nil { + PrintIndexerError("processChainTemplateAddedEvent", "Error converting chain id hex to int", templateIdHex, chainIdHex, chainTemplateHashHex, chainTemplatePositionHex, chainTemplateWidthHex, chainTemplateHeightHex) + return + } + + imageHashLowercase := strings.ToLower(chainTemplateHashHex) + + chainTemplatePosition, err := strconv.ParseInt(chainTemplatePositionHex, 0, 64) + if err != nil { + PrintIndexerError("processChainTemplateAddedEvent", "Error converting chain template position hex to int", templateIdHex, chainIdHex, chainTemplateHashHex, chainTemplatePositionHex, chainTemplateWidthHex, chainTemplateHeightHex) + return + } + + chainTemplateWidth, err := strconv.ParseInt(chainTemplateWidthHex, 0, 64) + if err != nil { + PrintIndexerError("processChainTemplateAddedEvent", "Error converting chain template width hex to int", templateIdHex, chainIdHex, chainTemplateHashHex, chainTemplatePositionHex, chainTemplateWidthHex, chainTemplateHeightHex) + return + } + + chainTemplateHeight, err := strconv.ParseInt(chainTemplateHeightHex, 0, 64) + if err != nil { + PrintIndexerError("processChainTemplateAddedEvent", "Error converting chain template height hex to int", templateIdHex, chainIdHex, chainTemplateHashHex, chainTemplatePositionHex, chainTemplateWidthHex, chainTemplateHeightHex) + return + } + + // Add chain template to postgres + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "INSERT INTO ChainFactionTemplates (template_id, faction_id, hash, position, width, height, stale) VALUES ($1, $2, $3, $4, $5, $6, $7)", templateId, chainId, imageHashLowercase, chainTemplatePosition, chainTemplateWidth, chainTemplateHeight, false) + if err != nil { + PrintIndexerError("processChainTemplateAddedEvent", "Error inserting chain template into postgres", templateIdHex, chainIdHex, chainTemplateHashHex, chainTemplatePositionHex, chainTemplateWidthHex, chainTemplateHeightHex) + return + } +} + +func revertChainFactionTemplateAddedEvent(event IndexerEvent) { + templateIdHex := event.Event.Keys[1] + + templateId, err := strconv.ParseInt(templateIdHex, 0, 64) + if err != nil { + PrintIndexerError("reverseChainTemplateAddedEvent", "Error converting template id hex to int", templateIdHex) + return + } + + // Remove chain template from postgres + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "DELETE FROM ChainFactionTemplates WHERE template_id = $1", templateId) + if err != nil { + PrintIndexerError("reverseChainTemplateAddedEvent", "Error deleting chain template from postgres", templateIdHex) + return + } +} + +func processChainFactionTemplateRemovedEvent(event IndexerEvent) { + templateIdHex := event.Event.Keys[1] + + templateId, err := strconv.ParseInt(templateIdHex, 0, 64) + if err != nil { + PrintIndexerError("processChainTemplateRemovedEvent", "Error converting template id hex to int", templateIdHex) + return + } + + // Mark chain template as stale in postgres + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "UPDATE ChainFactionTemplates SET stale = true WHERE template_id = $1", templateId) + if err != nil { + PrintIndexerError("processChainTemplateRemovedEvent", "Error marking chain template as stale in postgres", templateIdHex) + return + } +} + +func revertChainFactionTemplateRemovedEvent(event IndexerEvent) { + templateIdHex := event.Event.Keys[1] + + templateId, err := strconv.ParseInt(templateIdHex, 0, 64) + if err != nil { + PrintIndexerError("reverseChainTemplateRemovedEvent", "Error converting template id hex to int", templateIdHex) + return + } + + // Unmark chain template as stale in postgres + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "UPDATE ChainFactionTemplates SET stale = false WHERE template_id = $1", templateId) + if err != nil { + PrintIndexerError("reverseChainTemplateRemovedEvent", "Error unmarking chain template as stale in postgres", templateIdHex) + return + } +} diff --git a/backend/routes/templates.go b/backend/routes/templates.go index 5bc52af1..e36a5d17 100644 --- a/backend/routes/templates.go +++ b/backend/routes/templates.go @@ -2,7 +2,7 @@ package routes import ( "bytes" - "context" + "crypto/sha256" "fmt" "image" "image/color" @@ -15,33 +15,45 @@ import ( "strconv" "strings" - "github.com/NethermindEth/juno/core/crypto" - "github.com/NethermindEth/juno/core/felt" - "github.com/keep-starknet-strange/art-peace/backend/core" routeutils "github.com/keep-starknet-strange/art-peace/backend/routes/utils" ) func InitTemplateRoutes() { http.HandleFunc("/get-templates", getTemplates) + http.HandleFunc("/get-faction-templates", getFactionTemplates) + http.HandleFunc("/get-chain-faction-templates", getChainFactionTemplates) http.HandleFunc("/add-template-img", addTemplateImg) http.HandleFunc("/add-template-data", addTemplateData) if !core.ArtPeaceBackend.BackendConfig.Production { - http.HandleFunc("/add-template-devnet", addTemplateDevnet) + // http.HandleFunc("/add-template-devnet", addTemplateDevnet) + http.HandleFunc("/add-faction-template-devnet", addFactionTemplateDevnet) + http.HandleFunc("/remove-faction-template-devnet", removeFactionTemplateDevnet) + http.HandleFunc("/add-chain-faction-template-devnet", addChainFactionTemplateDevnet) + http.HandleFunc("/remove-chain-faction-template-devnet", removeChainFactionTemplateDevnet) } - http.Handle("/templates/", http.StripPrefix("/templates/", http.FileServer(http.Dir(".")))) + http.Handle("/templates/", http.StripPrefix("/templates/", http.FileServer(http.Dir("./templates/")))) } -// TODO: Add specific location for template images - func hashTemplateImage(pixelData []byte) string { - var data []*felt.Felt - for _, pixel := range pixelData { - f := new(felt.Felt).SetUint64(uint64(pixel)) - data = append(data, f) - } - hash := crypto.PoseidonArray(data...) - return hash.String() + /* + TODO: Implement Poseidon hash + "github.com/NethermindEth/juno/core/crypto" + "github.com/NethermindEth/juno/core/felt" + + var data []*felt.Felt + for _, pixel := range pixelData { + f := new(felt.Felt).SetUint64(uint64(pixel)) + data = append(data, f) + } + hash := crypto.PoseidonArray(data...) + */ + h := sha256.New() + h.Write(pixelData) + hash := h.Sum(nil) + hashStr := fmt.Sprintf("%x", hash) + // Replace 1st byte with 00 + return "00" + hashStr[2:] } func bytesToRGBA(colorBytes []byte) color.RGBA { @@ -126,6 +138,47 @@ func getTemplates(w http.ResponseWriter, r *http.Request) { routeutils.WriteDataJson(w, string(templates)) } +type FactionTemplateData struct { + TemplateId int `json:"templateId"` + Hash string `json:"hash"` + Width int `json:"width"` + Height int `json:"height"` + Position int `json:"position"` +} + +// TODO: Pagination +func getFactionTemplates(w http.ResponseWriter, r *http.Request) { + factionId, err := strconv.Atoi(r.URL.Query().Get("factionId")) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid faction ID") + return + } + + factionTemplates, err := core.PostgresQueryJson[FactionTemplateData]("SELECT template_id, hash, width, height, position FROM FactionTemplates WHERE faction_id = $1 AND stale = false", factionId) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get faction templates") + return + } + + routeutils.WriteDataJson(w, string(factionTemplates)) +} + +func getChainFactionTemplates(w http.ResponseWriter, r *http.Request) { + factionId, err := strconv.Atoi(r.URL.Query().Get("factionId")) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid faction ID") + return + } + + factionTemplates, err := core.PostgresQueryJson[FactionTemplateData]("SELECT template_id, hash, width, height, position FROM ChainFactionTemplates WHERE faction_id = $1 AND stale = false", factionId) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get chain faction templates") + return + } + + routeutils.WriteDataJson(w, string(factionTemplates)) +} + func addTemplateImg(w http.ResponseWriter, r *http.Request) { file, _, err := r.FormFile("image") if err != nil { @@ -142,7 +195,7 @@ func addTemplateImg(w http.ResponseWriter, r *http.Request) { } bounds := img.Bounds() width, height := bounds.Max.X-bounds.Min.X, bounds.Max.Y-bounds.Min.Y - if width < 5 || width > 50 || height < 5 || height > 50 { + if width < 5 || width > 64 || height < 5 || height > 64 { routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid image dimensions") return } @@ -164,14 +217,16 @@ func addTemplateImg(w http.ResponseWriter, r *http.Request) { return } hash := hashTemplateImage(imageData) - _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "INSERT INTO TemplateData (hash, data) VALUES ($1, $2)", hash, imageData) - if err != nil { - routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to insert template data in postgres") - return + + if _, err := os.Stat("templates"); os.IsNotExist(err) { + err = os.Mkdir("templates", os.ModePerm) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create templates directory") + return + } } - // TODO: Path to store generated image - filename := fmt.Sprintf("template-%s.png", hash) + filename := fmt.Sprintf("templates/template-%s.png", hash) newimg, err := os.Create(filename) if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create image file") @@ -209,7 +264,7 @@ func addTemplateData(w http.ResponseWriter, r *http.Request) { return } - if width < 5 || width > 50 || height < 5 || height > 50 { + if width < 5 || width > 64 || height < 5 || height > 64 { routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid image dimensions") return } @@ -234,11 +289,6 @@ func addTemplateData(w http.ResponseWriter, r *http.Request) { } hash := hashTemplateImage(imageBytes) - _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "INSERT INTO TemplateData (hash, data) VALUES ($1, $2)", hash, imageBytes) - if err != nil { - routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to insert template data in database") - return - } colorPaletteHex, err := core.PostgresQuery[string]("SELECT hex FROM colors ORDER BY color_key") if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get color palette") @@ -274,8 +324,15 @@ func addTemplateData(w http.ResponseWriter, r *http.Request) { } } - // TODO: Path to store generated image - filename := fmt.Sprintf("template-%s.png", hash) + if _, err := os.Stat("templates"); os.IsNotExist(err) { + err = os.Mkdir("templates", os.ModePerm) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create templates directory") + return + } + } + + filename := fmt.Sprintf("templates/template-%s.png", hash) file, err := os.Create(filename) if err != nil { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create image file") @@ -348,3 +405,163 @@ func addTemplateDevnet(w http.ResponseWriter, r *http.Request) { routeutils.WriteResultJson(w, "Template added to devnet") } + +func addFactionTemplateDevnet(w http.ResponseWriter, r *http.Request) { + // Disable this in production + if routeutils.NonProductionMiddleware(w, r) { + return + } + + jsonBody, err := routeutils.ReadJsonBody[map[string]string](r) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Failed to read request body") + return + } + + factionId, err := strconv.Atoi((*jsonBody)["factionId"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid faction ID") + return + } + + hash := (*jsonBody)["hash"] + + position, err := strconv.Atoi((*jsonBody)["position"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid position") + return + } + + width, err := strconv.Atoi((*jsonBody)["width"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid width") + return + } + + height, err := strconv.Atoi((*jsonBody)["height"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid height") + return + } + + shellCmd := core.ArtPeaceBackend.BackendConfig.Scripts.AddFactionTemplateDevnet + contract := os.Getenv("ART_PEACE_CONTRACT_ADDRESS") + cmd := exec.Command(shellCmd, contract, "add_faction_template", strconv.Itoa(factionId), hash, strconv.Itoa(position), strconv.Itoa(width), strconv.Itoa(height)) + _, err = cmd.Output() + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to add faction template to devnet") + return + } + + routeutils.WriteResultJson(w, "Faction template added to devnet") +} + +func removeFactionTemplateDevnet(w http.ResponseWriter, r *http.Request) { + // Disable this in production + if routeutils.NonProductionMiddleware(w, r) { + return + } + + jsonBody, err := routeutils.ReadJsonBody[map[string]string](r) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Failed to read request body") + return + } + + templateId, err := strconv.Atoi((*jsonBody)["templateId"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid template ID") + return + } + + shellCmd := core.ArtPeaceBackend.BackendConfig.Scripts.RemoveFactionTemplateDevnet + contract := os.Getenv("ART_PEACE_CONTRACT_ADDRESS") + cmd := exec.Command(shellCmd, contract, "remove_faction_template", strconv.Itoa(templateId)) + _, err = cmd.Output() + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to remove faction template from devnet") + return + } + + routeutils.WriteResultJson(w, "Faction template removed from devnet") +} + +func addChainFactionTemplateDevnet(w http.ResponseWriter, r *http.Request) { + // Disable this in production + if routeutils.NonProductionMiddleware(w, r) { + return + } + + jsonBody, err := routeutils.ReadJsonBody[map[string]string](r) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Failed to read request body") + return + } + + factionId, err := strconv.Atoi((*jsonBody)["factionId"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid faction ID") + return + } + + hash := (*jsonBody)["hash"] + + position, err := strconv.Atoi((*jsonBody)["position"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid position") + return + } + + width, err := strconv.Atoi((*jsonBody)["width"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid width") + return + } + + height, err := strconv.Atoi((*jsonBody)["height"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid height") + return + } + + shellCmd := core.ArtPeaceBackend.BackendConfig.Scripts.AddFactionTemplateDevnet + contract := os.Getenv("ART_PEACE_CONTRACT_ADDRESS") + cmd := exec.Command(shellCmd, contract, "add_chain_faction_template", strconv.Itoa(factionId), hash, strconv.Itoa(position), strconv.Itoa(width), strconv.Itoa(height)) + _, err = cmd.Output() + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to add chain faction template to devnet") + return + } + + routeutils.WriteResultJson(w, "Chain faction template added to devnet") +} + +func removeChainFactionTemplateDevnet(w http.ResponseWriter, r *http.Request) { + // Disable this in production + if routeutils.NonProductionMiddleware(w, r) { + return + } + + jsonBody, err := routeutils.ReadJsonBody[map[string]string](r) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Failed to read request body") + return + } + + templateId, err := strconv.Atoi((*jsonBody)["templateId"]) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid template ID") + return + } + + shellCmd := core.ArtPeaceBackend.BackendConfig.Scripts.RemoveFactionTemplateDevnet + contract := os.Getenv("ART_PEACE_CONTRACT_ADDRESS") + cmd := exec.Command(shellCmd, contract, "remove_chain_faction_template", strconv.Itoa(templateId)) + _, err = cmd.Output() + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to remove chain faction template from devnet") + return + } + + routeutils.WriteResultJson(w, "Chain faction template removed from devnet") +} diff --git a/backend/routes/user.go b/backend/routes/user.go index a269e0c8..767b1b40 100644 --- a/backend/routes/user.go +++ b/backend/routes/user.go @@ -22,6 +22,7 @@ func InitUserRoutes() { http.HandleFunc("/get-extra-pixels", getExtraPixels) http.HandleFunc("/get-username", getUsername) http.HandleFunc("/get-pixel-count", getPixelCount) + http.HandleFunc("/check-username-unique", checkUsernameUnique) if !core.ArtPeaceBackend.BackendConfig.Production { http.HandleFunc("/new-username-devnet", newUsernameDevnet) http.HandleFunc("/change-username-devnet", changeUsernameDevnet) @@ -172,7 +173,7 @@ func newUsernameDevnet(w http.ResponseWriter, r *http.Request) { return } - if len(username) > 31 { + if len(username) > 64 { routeutils.WriteErrorJson(w, http.StatusBadRequest, "Username too long (max 31 characters)") return } @@ -209,7 +210,7 @@ func changeUsernameDevnet(w http.ResponseWriter, r *http.Request) { return } - if len(username) > 31 { + if len(username) > 64 { routeutils.WriteErrorJson(w, http.StatusBadRequest, "Username too long (max 31 characters)") return } @@ -243,3 +244,19 @@ func getUserColorVote(w http.ResponseWriter, r *http.Request) { routeutils.WriteDataJson(w, strconv.Itoa(*vote)) } + +func checkUsernameUnique(w http.ResponseWriter, r *http.Request) { + username := r.URL.Query().Get("username") + if username == "" { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Missing username parameter") + return + } + + count, err := core.PostgresQueryOne[int]("SELECT COUNT(*) FROM Users WHERE name = $1", username) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to check username uniqueness") + return + } + + routeutils.WriteDataJson(w, strconv.Itoa(*count)) +} diff --git a/backend/routes/utils/responses.go b/backend/routes/utils/responses.go index a13963e8..da92db11 100644 --- a/backend/routes/utils/responses.go +++ b/backend/routes/utils/responses.go @@ -65,6 +65,7 @@ func SendWebSocketMessage(message map[string]interface{}) { fmt.Println("Failed to marshal websocket message") return } + core.ArtPeaceBackend.WSConnectionsLock.Lock() for idx, conn := range core.ArtPeaceBackend.WSConnections { if err := conn.WriteMessage(websocket.TextMessage, messageBytes); err != nil { fmt.Println(err) @@ -73,4 +74,5 @@ func SendWebSocketMessage(message map[string]interface{}) { core.ArtPeaceBackend.WSConnections = append(core.ArtPeaceBackend.WSConnections[:idx], core.ArtPeaceBackend.WSConnections[idx+1:]...) } } + core.ArtPeaceBackend.WSConnectionsLock.Unlock() } diff --git a/configs/backend.config.json b/configs/backend.config.json index 4f6bfe50..c9fdd122 100644 --- a/configs/backend.config.json +++ b/configs/backend.config.json @@ -13,7 +13,9 @@ "increase_day_devnet": "../tests/integration/local/increase_day_index.sh", "join_chain_faction_devnet": "../tests/integration/local/join_chain_faction.sh", "join_faction_devnet": "../tests/integration/local/join_faction.sh", - "leave_faction_devnet": "../tests/integration/local/leave_faction.sh" + "leave_faction_devnet": "../tests/integration/local/leave_faction.sh", + "add_faction_template_devnet": "../tests/integration/local/add_faction_template.sh", + "remove_faction_template_devnet": "../tests/integration/local/remove_faction_template.sh" }, "production": false, "websocket": { diff --git a/configs/canvas.config.json b/configs/canvas.config.json index 740b1749..f16844e6 100644 --- a/configs/canvas.config.json +++ b/configs/canvas.config.json @@ -1,7 +1,7 @@ { "canvas": { - "width": 256, - "height": 192 + "width": 512, + "height": 384 }, "colors": [ "FAFAFA", diff --git a/configs/docker-backend.config.json b/configs/docker-backend.config.json index 91600898..958a1874 100644 --- a/configs/docker-backend.config.json +++ b/configs/docker-backend.config.json @@ -16,7 +16,9 @@ "increase_day_devnet": "/scripts/increase_day_index.sh", "join_chain_faction_devnet": "/scripts/join_chain_faction.sh", "join_faction_devnet": "/scripts/join_faction.sh", - "leave_faction_devnet": "/scripts/leave_faction.sh" + "leave_faction_devnet": "/scripts/leave_faction.sh", + "add_faction_template_devnet": "/scripts/add_faction_template.sh", + "remove_faction_template_devnet": "/scripts/remove_faction_template.sh" }, "production": false, "websocket": { diff --git a/configs/factions-all.config.json b/configs/factions-all.config.json new file mode 100644 index 00000000..19394c30 --- /dev/null +++ b/configs/factions-all.config.json @@ -0,0 +1,125 @@ +{ + "factions": [ + { + "id": 1, + "name": "Early Birds", + "icon": "$BACKEND_URL/faction-images/early-bird.png", + "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "joinable": false, + "allocation": 2, + "links": { + "telegram": "", + "twitter": "", + "github": "", + "site": "" + }, + "members": [ + "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "0x034be07b6e7eeb280eb15d000d9eb53a63e5614e9886b74284991098c30a614a", + "0x013d851b4a0a69c86309d6e1f89a728c7e23d4dac3499531ec894a8f002a07dc", + "0x0583e9857d5547330859698f711a7bd354d88970cb8987ea4c6b084ce3567319", + "0x01bf5fad6815868d6fe067905548285596cf311641169544109a7a5394c2565f", + "0x02d053fa69add382b43095102488d89c5d49ca70df16fc39513a61da4352fd48", + "0x01c14041373cd03109c8a05c52fb45e3aff992b988a7237aab5196a5614fe326", + "0x0764b02d58af0bd15f6aa9555cb89402a62ca3f655f5ff5b2e2fe8041beb593d" + ] + }, + { + "id": 2, + "name": "Keep Starknet Strange", + "icon": "$BACKEND_URL/faction-images/keep-starknet-strange.png", + "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "joinable": false, + "allocation": 5, + "links": { + "telegram": "", + "twitter": "", + "github": "https://github.com/keep-starknet-strange", + "site": "" + }, + "members": [ + "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "0x0583e9857d5547330859698f711a7bd354d88970cb8987ea4c6b084ce3567319" + ] + }, + { + "id": 3, + "name": "Contributors", + "icon": "$BACKEND_URL/faction-images/contributors.png", + "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "joinable": false, + "allocation": 2, + "links": { + "telegram": "", + "twitter": "", + "github": "https://github.com/keep-starknet-strange/art-peace", + "site": "" + }, + "members": [ + "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "0x034be07b6e7eeb280eb15d000d9eb53a63e5614e9886b74284991098c30a614a", + "0x013d851b4a0a69c86309d6e1f89a728c7e23d4dac3499531ec894a8f002a07dc", + "0x0583e9857d5547330859698f711a7bd354d88970cb8987ea4c6b084ce3567319", + "0x01bf5fad6815868d6fe067905548285596cf311641169544109a7a5394c2565f" + ] + }, + { + "id": 4, + "name": "Ducks Everywhere", + "icon": "$BACKEND_URL/faction-images/ducks-everywhere.png", + "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "joinable": true, + "allocation": 1, + "links": { + "telegram": "https://t.me/duckseverywhere", + "twitter": "https://twitter.com/DucksEverywher2", + "github": "", + "site": "https://linktr.ee/duckseverywhere" + }, + "members": [ + ] + }, + { + "id": 5, + "name": "PixeLaw", + "icon": "$BACKEND_URL/faction-images/pixelaw.png", + "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "joinable": true, + "allocation": 1, + "links": { + "telegram": "https://t.me/pixelaw", + "twitter": "https://twitter.com/0xPixeLAW", + "github": "https://github.com/pixelaw", + "site": "https://www.pixelaw.xyz" + }, + "members": [ + ] + }, + { + "id": 6, + "name": "WASD", + "icon": "$BACKEND_URL/faction-images/wasd.png", + "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "joinable": true, + "allocation": 1, + "links": { + "telegram": "https://t.me/wasd", + "twitter": "https://twitter.com/WASD_0x", + "github": "", + "site": "https://bento.me/wasd" + }, + "members": [ + ] + } + ], + "chain_factions": [ + "Starknet", + "Solana", + "Bitcoin", + "Base", + "ZkSync", + "Polygon", + "Optimism", + "Scroll" + ] +} diff --git a/configs/factions.config.json b/configs/factions.config.json index 19394c30..9a63176c 100644 --- a/configs/factions.config.json +++ b/configs/factions.config.json @@ -2,111 +2,112 @@ "factions": [ { "id": 1, - "name": "Early Birds", - "icon": "$BACKEND_URL/faction-images/early-bird.png", + "name": "Ducks Everywhere", + "icon": "$BACKEND_URL/faction-images/ducks-everywhere.png", "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", - "joinable": false, - "allocation": 2, + "joinable": true, + "allocation": 1, "links": { "telegram": "", - "twitter": "", + "twitter": "https://x.com/DucksEverywher2", "github": "", - "site": "" + "site": "https://linktr.ee/duckseverywhere" }, "members": [ - "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", - "0x034be07b6e7eeb280eb15d000d9eb53a63e5614e9886b74284991098c30a614a", - "0x013d851b4a0a69c86309d6e1f89a728c7e23d4dac3499531ec894a8f002a07dc", - "0x0583e9857d5547330859698f711a7bd354d88970cb8987ea4c6b084ce3567319", - "0x01bf5fad6815868d6fe067905548285596cf311641169544109a7a5394c2565f", - "0x02d053fa69add382b43095102488d89c5d49ca70df16fc39513a61da4352fd48", - "0x01c14041373cd03109c8a05c52fb45e3aff992b988a7237aab5196a5614fe326", - "0x0764b02d58af0bd15f6aa9555cb89402a62ca3f655f5ff5b2e2fe8041beb593d" ] }, { "id": 2, - "name": "Keep Starknet Strange", - "icon": "$BACKEND_URL/faction-images/keep-starknet-strange.png", + "name": "WASD", + "icon": "$BACKEND_URL/faction-images/wasd.png", "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", - "joinable": false, - "allocation": 5, + "joinable": true, + "allocation": 1, "links": { "telegram": "", - "twitter": "", - "github": "https://github.com/keep-starknet-strange", - "site": "" + "twitter": "https://twitter.com/WASD_0x", + "github": "", + "site": "https://bento.me/wasd" }, "members": [ - "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", - "0x0583e9857d5547330859698f711a7bd354d88970cb8987ea4c6b084ce3567319" ] }, { "id": 3, - "name": "Contributors", - "icon": "$BACKEND_URL/faction-images/contributors.png", + "name": "Influence", + "icon": "$BACKEND_URL/faction-images/influence.png", "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", - "joinable": false, - "allocation": 2, + "joinable": true, + "allocation": 1, "links": { "telegram": "", - "twitter": "", - "github": "https://github.com/keep-starknet-strange/art-peace", - "site": "" + "twitter": "https://x.com/InfluenceNFT", + "github": "", + "site": "https://www.influenceth.io/" }, "members": [ - "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", - "0x034be07b6e7eeb280eb15d000d9eb53a63e5614e9886b74284991098c30a614a", - "0x013d851b4a0a69c86309d6e1f89a728c7e23d4dac3499531ec894a8f002a07dc", - "0x0583e9857d5547330859698f711a7bd354d88970cb8987ea4c6b084ce3567319", - "0x01bf5fad6815868d6fe067905548285596cf311641169544109a7a5394c2565f" ] }, { "id": 4, - "name": "Ducks Everywhere", - "icon": "$BACKEND_URL/faction-images/ducks-everywhere.png", + "name": "Ark Project", + "icon": "$BACKEND_URL/faction-images/ark-project.png", "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", "joinable": true, "allocation": 1, "links": { - "telegram": "https://t.me/duckseverywhere", - "twitter": "https://twitter.com/DucksEverywher2", + "telegram": "", + "twitter": "https://x.com/arkprojectnfts", "github": "", - "site": "https://linktr.ee/duckseverywhere" + "site": "https://www.arkproject.dev/" }, "members": [ ] }, { "id": 5, - "name": "PixeLaw", - "icon": "$BACKEND_URL/faction-images/pixelaw.png", + "name": "Everai", + "icon": "$BACKEND_URL/faction-images/everai.png", "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", "joinable": true, "allocation": 1, "links": { - "telegram": "https://t.me/pixelaw", - "twitter": "https://twitter.com/0xPixeLAW", - "github": "https://github.com/pixelaw", - "site": "https://www.pixelaw.xyz" + "telegram": "", + "twitter": "https://x.com/everai", + "github": "", + "site": "https://www.everai.xyz/" }, "members": [ ] }, { "id": 6, - "name": "WASD", - "icon": "$BACKEND_URL/faction-images/wasd.png", + "name": "2077 Collective", + "icon": "$BACKEND_URL/faction-images/2077.png", "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", "joinable": true, "allocation": 1, "links": { - "telegram": "https://t.me/wasd", - "twitter": "https://twitter.com/WASD_0x", + "telegram": "", + "twitter": "https://x.com/2077Collective", "github": "", - "site": "https://bento.me/wasd" + "site": "https://2077.xyz/" + }, + "members": [ + ] + }, + { + "id": 7, + "name": "Argent", + "icon": "$BACKEND_URL/faction-images/argent.png", + "leader": "0x07c313ea8b45044c2272b77ec7332b65bdfef089c4de0fffab3de3fd6b85d124", + "joinable": true, + "allocation": 1, + "links": { + "telegram": "", + "twitter": "https://x.com/argentHQ", + "github": "", + "site": "https://www.argent.xyz/" }, "members": [ ] @@ -120,6 +121,8 @@ "ZkSync", "Polygon", "Optimism", - "Scroll" + "Scroll", + "Arbitrum", + "Dogecoin" ] } diff --git a/docker-compose.yml b/docker-compose.yml index 347dfc28..a51a3e5e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,8 +34,7 @@ services: environment: - POSTGRES_PASSWORD=password - ART_PEACE_END_TIME=3000000000 - volumes: - - nfts:/app/nfts + - ART_PEACE_HOST=0328ced46664355fc4b885ae7011af202313056a7e3d44827fb24c9d3206aaa0 consumer: build: dockerfile: backend/Dockerfile.consumer diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0e45023d..087f7ae7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "@testing-library/user-event": "^13.5.0", "d3": "^7.9.0", "get-starknet-core": "^3.3.0", + "js-sha256": "^0.11.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-responsive": "^10.0.0", @@ -12557,6 +12558,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7b22f5ee..44c4cc9a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^13.5.0", "d3": "^7.9.0", "get-starknet-core": "^3.3.0", + "js-sha256": "^0.11.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-responsive": "^10.0.0", diff --git a/frontend/public/index.html b/frontend/public/index.html index 1d520461..ed2e976b 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -4,7 +4,7 @@ - + @@ -44,7 +44,7 @@ -