Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Canvas and more filters #267

Merged
29 changes: 24 additions & 5 deletions backend/routes/canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package routes

import (
"context"
"fmt"
"net/http"

"github.com/keep-starknet-strange/art-peace/backend/core"
Expand All @@ -19,7 +20,16 @@ func initCanvas(w http.ResponseWriter, r *http.Request) {
return
}

if core.ArtPeaceBackend.Databases.Redis.Exists(context.Background(), "canvas").Val() == 0 {
// Get round number from query params
roundNumber := r.URL.Query().Get("round")
b-j-roberts marked this conversation as resolved.
Show resolved Hide resolved
if roundNumber == "" {
routeutils.WriteErrorJson(w, http.StatusBadRequest, "Round number is required")
return
}

canvasKey := fmt.Sprintf("canvas-%s", roundNumber)

if core.ArtPeaceBackend.Databases.Redis.Exists(context.Background(), canvasKey).Val() == 0 {
totalBitSize := core.ArtPeaceBackend.CanvasConfig.Canvas.Width * core.ArtPeaceBackend.CanvasConfig.Canvas.Height * core.ArtPeaceBackend.CanvasConfig.ColorsBitWidth
totalByteSize := (totalBitSize / 8)
if totalBitSize%8 != 0 {
Expand All @@ -30,23 +40,32 @@ func initCanvas(w http.ResponseWriter, r *http.Request) {
// Create canvas
canvas := make([]byte, totalByteSize)
ctx := context.Background()
err := core.ArtPeaceBackend.Databases.Redis.Set(ctx, "canvas", canvas, 0).Err()
err := core.ArtPeaceBackend.Databases.Redis.Set(ctx, canvasKey, canvas, 0).Err()
if err != nil {
routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to initialize canvas")
return
}

routeutils.WriteResultJson(w, "Canvas initialized")
routeutils.WriteResultJson(w, fmt.Sprintf("Canvas for round %s initialized", roundNumber))
} else {
routeutils.WriteErrorJson(w, http.StatusConflict, "Canvas already initialized")
routeutils.WriteErrorJson(w, http.StatusConflict, fmt.Sprintf("Canvas for round %s already initialized", roundNumber))
}
}

func getCanvas(w http.ResponseWriter, r *http.Request) {
routeutils.SetupAccessHeaders(w)

// Get round number from query params, default to latest round if not specified
roundNumber := r.URL.Query().Get("round")
if roundNumber == "" {
// TODO: Implement logic to get latest round number
roundNumber = "1" // Default to round 1 for now
b-j-roberts marked this conversation as resolved.
Show resolved Hide resolved
}

canvasKey := fmt.Sprintf("canvas-%s", roundNumber)

ctx := context.Background()
val, err := core.ArtPeaceBackend.Databases.Redis.Get(ctx, "canvas").Result()
val, err := core.ArtPeaceBackend.Databases.Redis.Get(ctx, canvasKey).Result()
if err != nil {
routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to get canvas")
return
Expand Down
55 changes: 55 additions & 0 deletions backend/routes/nft.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func InitNFTRoutes() {
http.HandleFunc("/get-nft-pixel-data", getNftPixelData)
// http.HandleFunc("/like-nft", LikeNFT)
// http.HandleFunc("/unlike-nft", UnLikeNFT)
http.HandleFunc("/get-liked-nfts", getLikedNFTs)
http.HandleFunc("/get-top-nfts", getTopNFTs)
http.HandleFunc("/get-hot-nfts", getHotNFTs)
if !core.ArtPeaceBackend.BackendConfig.Production {
Expand Down Expand Up @@ -545,3 +546,57 @@ func getHotNFTs(w http.ResponseWriter, r *http.Request) {
}
routeutils.WriteDataJson(w, string(nfts))
}

func getLikedNFTs(w http.ResponseWriter, r *http.Request) {
address := r.URL.Query().Get("address")
if address == "" || address == "0" {
routeutils.WriteErrorJson(w, http.StatusBadRequest, "Valid address required for liked NFTs")
return
}

pageLength, err := strconv.Atoi(r.URL.Query().Get("pageLength"))
if err != nil || pageLength <= 0 {
pageLength = 25
}
if pageLength > 50 {
pageLength = 50
}

page, err := strconv.Atoi(r.URL.Query().Get("page"))
if err != nil || page <= 0 {
page = 1
}
offset := (page - 1) * pageLength

query := `
SELECT
nfts.*,
COALESCE(like_count, 0) AS likes,
true as liked
FROM
nfts
LEFT JOIN (
SELECT
nftKey,
COUNT(*) AS like_count
FROM
nftlikes
GROUP BY
nftKey
) nftlikes ON nfts.token_id = nftlikes.nftKey
WHERE
nfts.token_id IN (
SELECT nftkey
FROM nftlikes
WHERE liker = $1
)
ORDER BY nfts.token_id DESC
LIMIT $2 OFFSET $3`

nfts, err := core.PostgresQueryJson[NFTData](query, address, pageLength, offset)
if err != nil {
routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to retrieve liked NFTs")
return
}
routeutils.WriteDataJson(w, string(nfts))
}
7 changes: 7 additions & 0 deletions frontend/src/services/apiService.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,10 @@ export const getFactionMembers = async (query) => {
`get-faction-members?factionId=${query.factionId}&page=${query.page}&pageLength=${query.pageLength}`
);
};

export const getLikedNftsFn = async (params) => {
const { page, pageLength, queryAddress } = params;
return await fetchWrapper(
`get-liked-nfts?address=${queryAddress}&page=${page}&pageLength=${pageLength}`
);
};
89 changes: 55 additions & 34 deletions frontend/src/tabs/nfts/NFTs.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
getNftsFn,
getNewNftsFn,
getTopNftsFn,
getHotNftsFn
getHotNftsFn,
getLikedNftsFn
} from '../../services/apiService.js';
import { PaginationView } from '../../ui/pagination.js';

Expand Down Expand Up @@ -94,61 +95,76 @@ const NFTsMainSection = (props) => {
};

const NFTsExpandedSection = (props) => {
const roundNumber = process.env.REACT_APP_ROUND_NUMBER || '0';
const imageURL = `${nftUrl}/nft/round-${roundNumber}/images/`;
const metadataURL = `${nftUrl}/nft/round-${roundNumber}/metadata/`;
const maxRound = parseInt(process.env.REACT_APP_ROUND_NUMBER || '1');
const [currentRound, setCurrentRound] = useState(maxRound);
const imageURL = `${nftUrl}/nft/round-${currentRound}/images/`;
const metadataURL = `${nftUrl}/nft/round-${currentRound}/metadata/`;

const handleRoundChange = (direction) => {
if (direction === 'prev' && currentRound > 1) {
setCurrentRound((prev) => prev - 1);
props.setAllNftPagination((prev) => ({ ...prev, page: 1 }));
} else if (direction === 'next' && currentRound < maxRound) {
setCurrentRound((prev) => prev + 1);
props.setAllNftPagination((prev) => ({ ...prev, page: 1 }));
}
};

return (
<div className='NFTs__all'>
<div className='NFTs__header'>
<h2 className='NFTs__heading'>Explore</h2>
<div className='NFTs__filters'>
{props.filters.map((filter, index) => {
return (
<div
key={index}
className={`NFTs__button NFTs__filter ${
props.activeFilter === filter ? 'NFTs__button--selected' : ''
}`}
onClick={() => props.setActiveFilter(filter)}
>
{filter}
</div>
);
})}
{props.filters.map((filter, index) => (
<div
key={index}
className={`NFTs__button NFTs__filter ${
props.activeFilter === filter ? 'NFTs__button--selected' : ''
}`}
onClick={() => props.setActiveFilter(filter)}
>
{filter}
</div>
))}
<div
className='NFTs__button NFTs__filter'
onClick={() => handleRoundChange('prev')}
>
{'<'}
</div>
<div className='NFTs__button NFTs__filter'>
{`Round ${currentRound}`}
</div>
<div
className='NFTs__button NFTs__filter'
onClick={() => handleRoundChange('next')}
>
{'>'}
</div>
</div>
</div>

<div className='NFTs__all__container'>
<div className='NFTs__all__grid'>
{props.allNfts.map((nft, index) => {
return (
{currentRound === maxRound &&
(props.allNfts.length > 0 || props.activeFilter !== 'liked') &&
props.allNfts.map((nft, index) => (
<NFTItem
key={index}
{...nft}
address={props.address}
account={props.account}
estimateInvokeFee={props.estimateInvokeFee}
artPeaceContract={props.artPeaceContract}
canvasNftContract={props.canvasNftContract}
tokenId={nft.tokenId}
position={nft.position}
image={imageURL + 'nft-' + nft.tokenId + '.png'}
metadata={metadataURL + 'nft-' + nft.tokenId + '.json'}
width={nft.width}
height={nft.height}
name={nft.name}
blockNumber={nft.blockNumber}
likes={nft.likes}
liked={nft.liked}
minter={nft.minter}
queryAddress={props.queryAddress}
updateLikes={props.updateLikes}
setTemplateOverlayMode={props.setTemplateOverlayMode}
setOverlayTemplate={props.setOverlayTemplate}
setActiveTab={props.setActiveTab}
/>
);
})}
))}
</div>
<PaginationView
data={props.allNfts}
Expand Down Expand Up @@ -178,7 +194,6 @@ const NFTs = (props) => {
const response = await fetchWrapper(getNFTByTokenId, { mode: 'cors' });
if (response.data) {
let newNFTs = [response.data, ...myNFTs];
// Remove duplicate tokenIds
let uniqueNFTs = newNFTs.filter(
(nft, index, self) =>
index === self.findIndex((t) => t.tokenId === nft.tokenId)
Expand Down Expand Up @@ -244,7 +259,7 @@ const NFTs = (props) => {
}, [props.queryAddress, myNftPagination.page, myNftPagination.pageLength]);

const [expanded, setExpanded] = useState(false);
const filters = ['hot', 'new', 'top'];
const filters = ['hot', 'new', 'top', 'liked'];
const [activeFilter, setActiveFilter] = useState(filters[0]);

useEffect(() => {
Expand Down Expand Up @@ -272,6 +287,12 @@ const NFTs = (props) => {
pageLength: allNftPagination.pageLength,
queryAddress: props.queryAddress
});
} else if (activeFilter === 'liked') {
result = await getLikedNftsFn({
page: allNftPagination.page,
pageLength: allNftPagination.pageLength,
queryAddress: props.queryAddress
});
} else {
result = await getNftsFn({
page: allNftPagination.page,
Expand All @@ -298,7 +319,7 @@ const NFTs = (props) => {
}
}
getNfts();
}, [props.queryAddress, expanded, allNftPagination]);
}, [props.queryAddress, expanded, allNftPagination, activeFilter]);

const resetPagination = () => {
setAllNftPagination((prev) => ({
Expand Down
Loading