diff --git a/src/main/java/com/memesphere/domain/chat/controller/ChatController.java b/src/main/java/com/memesphere/domain/chat/controller/ChatController.java index a0c71eee..e315d4e2 100644 --- a/src/main/java/com/memesphere/domain/chat/controller/ChatController.java +++ b/src/main/java/com/memesphere/domain/chat/controller/ChatController.java @@ -4,16 +4,18 @@ import com.memesphere.domain.chat.dto.response.ChatResponse; import com.memesphere.domain.chat.service.ChatService; import com.memesphere.global.apipayload.ApiResponse; +import com.memesphere.global.jwt.CustomUserDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.handler.annotation.SendTo; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; @Tag(name = "실시간 채팅", description = "실시간 채팅 관련 API") @RestController @@ -22,16 +24,25 @@ public class ChatController { private final ChatService chatService; - @MessageMapping("/chat/{coin_id}") - @SendTo("/sub/{coin_id}") + @MessageMapping("/chat/{coin_id}") // 클라이언트가 pub/chat/{coin_id}로 STOMP 메시지를 전송하면 실행됨 + @SendTo("/sub/{coin_id}") // 메서드가 반환한 데이터를 구독 중인 클라이언트에게 전송 public ChatResponse chat(@DestinationVariable("coin_id") Long coin_id, @Payload ChatRequest chatRequest) { return chatService.saveMessage(coin_id, chatRequest); } + @GetMapping("/chat/{coin_id}/list") + @Operation(summary = "코인별 채팅 전체 메시지 조회 API", + description = "특정 코인의 채팅방의 전체 메시지를 보여줍니다.") + public ApiResponse> getChatList(@PathVariable("coin_id") Long coin_id, + Pageable pageable) { + + return ApiResponse.onSuccess(chatService.getChatList(coin_id, pageable)); + } + //최신 댓글 조회 Api - @GetMapping("/latest/{coin_id}") + @GetMapping("/chat/{coin_id}/latest") @Operation(summary = "코인별 최신 댓글 조회 API", description = "특정 코인에 대한 최신 댓글을 반환합니다. 요청 시 최신 댓글 하나만 가져옵니다.") public ApiResponse getLatestMessages( @@ -42,4 +53,16 @@ public ApiResponse getLatestMessages( return ApiResponse.onSuccess(latestMessage); } + + @PostMapping("/chat/{coin_id}/like") + @Operation(summary = "채팅 좋아요 관련 API", + description = "채팅에서 하트를 클릭 시 하트가 채워지고, 좋아요 누른 사람의 숫자에 반영됩니다.") + public ApiResponse postChatLike(@PathVariable("coin_id") Long coin_id, + @RequestParam(value = "chat_id", required = true) Long chat_id, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + + Long userId = customUserDetails.getUser().getId(); + + return ApiResponse.onSuccess(chatService.postChatLike(chat_id, userId)); + } } diff --git a/src/main/java/com/memesphere/domain/chat/converter/ChatConverter.java b/src/main/java/com/memesphere/domain/chat/converter/ChatConverter.java index d86fe44f..6ab763aa 100644 --- a/src/main/java/com/memesphere/domain/chat/converter/ChatConverter.java +++ b/src/main/java/com/memesphere/domain/chat/converter/ChatConverter.java @@ -1,16 +1,18 @@ package com.memesphere.domain.chat.converter; import com.memesphere.domain.chat.entity.Chat; +import com.memesphere.domain.chat.entity.ChatLike; import com.memesphere.domain.memecoin.entity.MemeCoin; import com.memesphere.domain.chat.dto.request.ChatRequest; import com.memesphere.domain.chat.dto.response.ChatResponse; +import com.memesphere.domain.user.entity.User; public class ChatConverter { - public static Chat toChat(MemeCoin memeCoin, ChatRequest chatRequest) { + public static Chat toChat(MemeCoin memeCoin, ChatRequest chatRequest, User user) { return Chat.builder() -// .user(user) + .user(user) .memeCoin(memeCoin) .message(chatRequest.getMessage()) .build(); @@ -27,4 +29,11 @@ public static ChatResponse toChatResponse(Chat chat) { .nickname(chat.getUser().getNickname()) .build(); } + + public static ChatLike toChatLike(Chat chat, User user) { + return ChatLike.builder() + .user(user) + .chat(chat) + .build(); + } } diff --git a/src/main/java/com/memesphere/domain/chat/dto/request/ChatRequest.java b/src/main/java/com/memesphere/domain/chat/dto/request/ChatRequest.java index bec1eb6e..ce425b65 100644 --- a/src/main/java/com/memesphere/domain/chat/dto/request/ChatRequest.java +++ b/src/main/java/com/memesphere/domain/chat/dto/request/ChatRequest.java @@ -10,6 +10,9 @@ @NoArgsConstructor public class ChatRequest { - @Schema(description = "메시지 내용") + @Schema(description = "닉네임", example = "코인전문가") + private String nickname; + + @Schema(description = "메시지 내용", example = "도지코인 현재 얼마인가요?") private String message; } diff --git a/src/main/java/com/memesphere/domain/chat/entity/Chat.java b/src/main/java/com/memesphere/domain/chat/entity/Chat.java index 3141681e..231851b6 100644 --- a/src/main/java/com/memesphere/domain/chat/entity/Chat.java +++ b/src/main/java/com/memesphere/domain/chat/entity/Chat.java @@ -25,7 +25,7 @@ public class Chat extends BaseEntity { @Column(name="chat_id") private Long id; - @Column + @Column(columnDefinition = "varchar(500)", nullable = false) private String message; @ManyToOne(fetch=FetchType.LAZY) diff --git a/src/main/java/com/memesphere/domain/chat/repository/ChatCustomRepository.java b/src/main/java/com/memesphere/domain/chat/repository/ChatCustomRepository.java deleted file mode 100644 index ce1a884d..00000000 --- a/src/main/java/com/memesphere/domain/chat/repository/ChatCustomRepository.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.memesphere.domain.chat.repository; - -public interface ChatCustomRepository { -} diff --git a/src/main/java/com/memesphere/domain/chat/repository/ChatCustomRepositoryImpl.java b/src/main/java/com/memesphere/domain/chat/repository/ChatCustomRepositoryImpl.java deleted file mode 100644 index e817077b..00000000 --- a/src/main/java/com/memesphere/domain/chat/repository/ChatCustomRepositoryImpl.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.memesphere.domain.chat.repository; - -public class ChatCustomRepositoryImpl implements ChatCustomRepository { - -} \ No newline at end of file diff --git a/src/main/java/com/memesphere/domain/chat/repository/ChatLikeRepository.java b/src/main/java/com/memesphere/domain/chat/repository/ChatLikeRepository.java new file mode 100644 index 00000000..6111e6bb --- /dev/null +++ b/src/main/java/com/memesphere/domain/chat/repository/ChatLikeRepository.java @@ -0,0 +1,13 @@ +package com.memesphere.domain.chat.repository; + +import com.memesphere.domain.chat.entity.Chat; +import com.memesphere.domain.chat.entity.ChatLike; +import com.memesphere.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ChatLikeRepository extends JpaRepository { + + Optional findByChatAndUser(Chat chat, User user); +} diff --git a/src/main/java/com/memesphere/domain/chat/repository/ChatRepository.java b/src/main/java/com/memesphere/domain/chat/repository/ChatRepository.java index a8ec1d49..6c1869b4 100644 --- a/src/main/java/com/memesphere/domain/chat/repository/ChatRepository.java +++ b/src/main/java/com/memesphere/domain/chat/repository/ChatRepository.java @@ -1,9 +1,14 @@ package com.memesphere.domain.chat.repository; import com.memesphere.domain.chat.entity.Chat; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -public interface ChatRepository extends JpaRepository, ChatCustomRepository { +import java.util.List; + +public interface ChatRepository extends JpaRepository { Chat findFirstByMemeCoin_IdOrderByCreatedAtDesc(Long coin_id); + Page findAllByMemeCoin_Id(Long coin_id, Pageable pageable); } diff --git a/src/main/java/com/memesphere/domain/chat/service/ChatService.java b/src/main/java/com/memesphere/domain/chat/service/ChatService.java index 5a04ac2a..c5989f9f 100644 --- a/src/main/java/com/memesphere/domain/chat/service/ChatService.java +++ b/src/main/java/com/memesphere/domain/chat/service/ChatService.java @@ -2,6 +2,10 @@ import com.memesphere.domain.chat.converter.ChatConverter; import com.memesphere.domain.chat.entity.Chat; +import com.memesphere.domain.chat.entity.ChatLike; +import com.memesphere.domain.chat.repository.ChatLikeRepository; +import com.memesphere.domain.user.entity.User; +import com.memesphere.domain.user.repository.UserRepository; import com.memesphere.global.apipayload.code.status.ErrorStatus; import com.memesphere.global.apipayload.exception.GeneralException; import com.memesphere.domain.memecoin.entity.MemeCoin; @@ -10,7 +14,12 @@ import com.memesphere.domain.memecoin.repository.MemeCoinRepository; import com.memesphere.domain.chat.repository.ChatRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; @Service @RequiredArgsConstructor @@ -18,22 +27,34 @@ public class ChatService { private final MemeCoinRepository memeCoinRepository; private final ChatRepository chatRepository; + private final UserRepository userRepository; + private final ChatLikeRepository chatLikeRepository; public ChatResponse saveMessage(Long coin_id, ChatRequest chatRequest) { MemeCoin memeCoin = memeCoinRepository.findById(coin_id) .orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND)); - Chat chat = ChatConverter.toChat(memeCoin, chatRequest); + User user = userRepository.findByNickname(chatRequest.getNickname()) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + Chat chat = ChatConverter.toChat(memeCoin, chatRequest, user); Chat savedChat = chatRepository.save(chat); return ChatConverter.toChatResponse(savedChat); } + @Transactional + public Page getChatList(Long coin_id, Pageable pageable) { + + Page chatPage = chatRepository.findAllByMemeCoin_Id(coin_id, pageable); + return chatPage.map(ChatConverter::toChatResponse); + } + // 최신 댓글을 가져오는 메서드 + @Transactional public ChatResponse getLatestMessages(Long coin_id) { - MemeCoin memeCoin = memeCoinRepository.findById(coin_id) //id에 해당하는 밈코인 없을 때 .orElseThrow(() -> new GeneralException(ErrorStatus.MEMECOIN_NOT_FOUND)); @@ -50,4 +71,27 @@ public ChatResponse getLatestMessages(Long coin_id) { return ChatConverter.toChatResponse(latestChat); } + @Transactional + public String postChatLike(Long chat_id, Long user_id) { + + User user = userRepository.findById(user_id) + .orElseThrow(() -> new GeneralException(ErrorStatus.USER_NOT_FOUND)); + + Chat chat = chatRepository.findById(chat_id) + .orElseThrow(() -> new GeneralException(ErrorStatus.CHAT_NOT_FOUND)); + + // 사용자가 좋아요 눌렀는지 확인 + Optional existingLike = chatLikeRepository.findByChatAndUser(chat, user); + System.out.println("좋아요" + existingLike.isPresent()); + + if (existingLike.isPresent()) { + chatLikeRepository.delete(existingLike.get()); + return "좋아요를 취소했습니다."; + } else { + ChatLike chatLike = ChatConverter.toChatLike(chat, user); + chatLikeRepository.save(chatLike); + return "좋아요를 눌렀습니다."; + } + } + } diff --git a/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java b/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java index cafdb471..52f5252f 100644 --- a/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java +++ b/src/main/java/com/memesphere/global/apipayload/code/status/ErrorStatus.java @@ -45,7 +45,10 @@ public enum ErrorStatus implements BaseCode { // 이미지 에러 INVALID_FILE_EXTENTION(HttpStatus.BAD_REQUEST, "INVALID FILE EXTENSION", "지원되지 않는 파일 형식입니다."), - PRESIGNED_URL_FAILED(HttpStatus.BAD_REQUEST, "PRESIGNED URL GENERATION FAILED", "presigned URL 생성에 실패했습니다."); + PRESIGNED_URL_FAILED(HttpStatus.BAD_REQUEST, "PRESIGNED URL GENERATION FAILED", "presigned URL 생성에 실패했습니다."), + + // 채팅 에러 + CHAT_NOT_FOUND(HttpStatus.NOT_FOUND, "CHAT NOT FOUND", "채팅을 찾을 수 없습니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/memesphere/global/config/StompWebSocketConfig.java b/src/main/java/com/memesphere/global/config/StompWebSocketConfig.java index 52d7a7c4..fdf41790 100644 --- a/src/main/java/com/memesphere/global/config/StompWebSocketConfig.java +++ b/src/main/java/com/memesphere/global/config/StompWebSocketConfig.java @@ -1,9 +1,7 @@ package com.memesphere.global.config; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @@ -19,11 +17,8 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/ws") - .setAllowedOrigins("http://localhost:8080") - .withSockJS(); // ws, wss 대신 http, https를 통해 웹 소켓 연결하도록 함 - registry.addEndpoint("/connection") - .setAllowedOriginPatterns("*"); + registry.addEndpoint("/ws") //socket 연결 url: ws://localhost:8008/ws + .setAllowedOrigins("*"); } /* @@ -33,14 +28,9 @@ public void registerStompEndpoints(StompEndpointRegistry registry) { public void configureMessageBroker(MessageBrokerRegistry registry) { //sub 으로 시작되는 요청을 구독한 모든 사용자들에게 메시지를 broadcast한다. - registry.enableSimpleBroker("/sub"); + registry.enableSimpleBroker("/sub"); // 구독 url //pub로 시작되는 메시지는 message-handling method로 라우팅된다. //클라이언트가 서버로 전송하는 메세지의 경로 앞에 /pub 붙임 - registry.setApplicationDestinationPrefixes("/pub"); + registry.setApplicationDestinationPrefixes("/pub"); // prefix 정의 } - -// @Bean -// public WebSocketHandler webSocketHandler() { -// return new WebSocketHandler(); -// } } diff --git a/src/main/resources/memecoin-storage/memecoin.json b/src/main/resources/memecoin-storage/memecoin.json index cba6293f..c4d9163f 100644 --- a/src/main/resources/memecoin-storage/memecoin.json +++ b/src/main/resources/memecoin-storage/memecoin.json @@ -2,168 +2,168 @@ { "name": "Dogecoin", "symbol": "DOGE", - "image": "memecoin-img/doge.png", - "description": "Dogecoin", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/doge.png", + "description": "2013년에 시작된 세계 최초의 밈코인으로, 시바견 밈에서 영감을 받아 만들어졌습니다.", "collectionActive": false, "keywords": ["Animal", "Hot", "Accessible"] }, { "name": "SHIBA INU", "symbol": "SHIB", - "image": "memecoin-img/shib.png", - "description": "SHIBA INU", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/shib.png", + "description": "'도지코인 킬러'를 목표로 등장한 ERC-20 기반 밈코인으로, NFT와 탈중앙화 거래소 ShibaSwap을 통해 생태계를 확장하고 있습니다.", "collectionActive": false, "keywords": ["Animal", "Inu", "Trending"] }, { "name": "OFFICIAL TRUMP", "symbol": "TRUMP", - "image": "memecoin-img/trump.png", - "description": "OFFICIAL TRUMP", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/trump.png", + "description": "도널드 트럼프를 테마로 한 정치적 밈코인입니다.", "collectionActive": false, "keywords": ["Political", "Meme", "Viral"] }, { "name": "Pepe", "symbol": "PEPE", - "image": "memecoin-img/pepe.png", - "description": "Pepe", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/pepe.png", + "description": "인기 밈 캐릭터 '페페 개구리'를 기반으로 한 밈코인입니다.", "collectionActive": false, "keywords": ["Meme", "Ethereum", "No-Tax"] }, { "name": "Bonk", "symbol": "BONK", - "image": "memecoin-img/bonk.png", - "description": "Bonk", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/bonk.png", + "description": "Solana 생태계에서 출시된 커뮤니티 주도형 코인으로, 사용자를 위한 \"솔라나의 도지코인\"을 목표로 합니다.", "collectionActive": false, "keywords": ["Animal", "Solana", "Airdrop"] }, { "name": "Pudgy Penguins", "symbol": "PENGU", - "image": "memecoin-img/pengu.png", - "description": "Pudgy Penguins", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/pengu.png", + "description": "NFT 프로젝트와 연결되어 귀여운 펭귄 캐릭터로 커뮤니티와 수집성을 강화하고자 발행되었습니다.", "collectionActive": false, "keywords": ["Animal", "NFT", "Collectibles"] }, { "name": "Dogwifhat", "symbol": "WIF", - "image": "memecoin-img/wif.png", - "description": "Dogwifhat", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/wif.png", + "description": "모자를 쓴 강아지라는 독특한 캐릭터로 재미와 시각적 매력을 강조한 밈코인입니다.", "collectionActive": false, "keywords": ["Animal", "Solana", "Community"] }, { "name": "FLOKI", "symbol": "FLOKI", - "image": "memecoin-img/floki.png", - "description": "FLOKI", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/floki.png", + "description": "일론 머스크가 자신의 시바견 이름을 따서 언급한 뒤 탄생한 밈코인으로, NFT 게임 및 메타버스 프로젝트와 연결되어 있습니다.", "collectionActive": false, "keywords": ["Animal", "Elon Musk", "Web3"] }, { "name": "aixbt by Virtuals", "symbol": "AIXBT", - "image": "memecoin-img/aixbt.png", - "description": "aixbt by Virtuals", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/aixbt.png", + "description": "AI 기술과 가상세계 테마를 결합하여 창의적인 암호화폐 경험을 제공합니다.", "collectionActive": false, "keywords": ["AI", "Market Intelligence", "Community"] }, { "name": "Turbo", "symbol": "TURBO", - "image": "memecoin-img/turbo.png", - "description": "Turbo", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/turbo.png", + "description": "고속 개발과 커뮤니티 중심으로 성장한 코인으로, 창작자와 밈 애호가들에게 인기가 있습니다.", "collectionActive": false, "keywords": ["AI", "Decentralized", "Fast"] }, { "name": "ORDI", "symbol": "ORDI", - "image": "memecoin-img/ordi.png", - "description": "ORDI", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/ordi.png", + "description": "비트코인 네트워크 상에서 오디널스(Ordinals) 프로토콜을 활용해 만들어져 희소성을 지녔습니다.", "collectionActive": false, "keywords": ["Bitcoin", "Ordinals", "Tokenization"] }, { "name": "Peanut the Squirrel", "symbol": "PNUT", - "image": "memecoin-img/pnut.png", - "description": "Peanut the Squirrel", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/pnut.png", + "description": "다람쥐 캐릭터 '피넛'을 활용한 재미와 독창성이 돋보이는 밈코인으로, 소셜 플랫폼에서 인기를 얻었습니다.", "collectionActive": false, "keywords": ["Animal", "Viral", "Tribute"] }, { "name": "1M x BABYDOGE", "symbol": "1MBABYDOGE", - "image": "memecoin-img/babydoge.png", - "description": "1M x BABYDOGE", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/babydoge.png", + "description": "BabyDoge의 대량 버전으로, 커뮤니티의 대규모 지지를 받고 있습니다.", "collectionActive": false, "keywords": ["Animal", "Deflationary", "Meme"] }, { "name": "Memecoin", "symbol": "MEME", - "image": "memecoin-img/meme.png", - "description": "Memecoin", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/meme.png", + "description": "인터넷 밈 문화를 암호화폐에 접목하여 밈 자체가 화폐로 작동하도록 설계되었습니다.", "collectionActive": false, "keywords": ["Meme", "Web3", "SocialFi"] }, { "name": "BOOK OF MEME", "symbol": "BOME", - "image": "memecoin-img/bome.jpeg", - "description": "BOOK OF MEME", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/bome.jpeg", + "description": "밈 문화를 기록하고 보존하는 독특한 컨셉의 코인으로, NFT와 결합된 활용 가능성을 가집니다.", "collectionActive": false, "keywords": ["Meme", "Solana", "Decentralized"] }, { "name": "1000*SATS (Ordinals)", "symbol": "1000SATS", - "image": "memecoin-img/sats.png", - "description": "1000*SATS (Ordinals)", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/sats.png", + "description": "비트코인 오디널스 생태계의 일부로, 1,000 사토시(비트코인의 단위) 기반으로 발행된 상징적인 토큰입니다.", "collectionActive": false, "keywords": ["Bitcoin", "Tokenization", "Mining"] }, { "name": "First Neiro On Ethereum", "symbol": "NEIRO", - "image": "memecoin-img/neiro.jpeg", - "description": "First Neiro On Ethereum", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/neiro.jpeg", + "description": "이더리움 네트워크 상에서 인공지능 과 연결된 첫 번째 밈 프로젝트입니다.", "collectionActive": false, "keywords": ["Animal", "Ethereum", "Community-Driven"] }, { "name": "Dogs", "symbol": "DOGS", - "image": "memecoin-img/dogs.jpeg", - "description": "Dogs", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/dogs.jpeg", + "description": "다양한 강아지 밈을 통합하며 커뮤니티와 유머 중심으로 개발된 토큰입니다.", "collectionActive": false, "keywords": ["Animal", "Telegram", "Charity"] }, { "name": "ConstitutionDAO", "symbol": "PEOPLE", - "image": "memecoin-img/people.png", - "description": "ConstitutionDAO", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/people.png", + "description": "미국 헌법 원본 구매를 목표로 모금 운동을 벌이며, 대표적인 커뮤니티 기반의 밈코인 성공 사례입니다.", "collectionActive": false, "keywords": ["DAO", "Ethereum", "Crowdfunding"] }, { "name": "Act I: The AI Prophecy", "symbol": "ACT", - "image": "memecoin-img/act1.png", - "description": "Act I: The AI Prophecy", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/act1.png", + "description": "AI가 주도하는 미래를 밈과 예언적 스토리라인으로 표현한 독특한 프로젝트입니다.", "collectionActive": false, "keywords": ["AI", "Decentralized", "Community"] }, { "name": "1000*Simons Cat", "symbol": "1000CAT", - "image": "memecoin-img/simons-cat.png", - "description": "1000*Simons Cat", + "image": "https://umc-meme.s3.ap-northeast-2.amazonaws.com/meme-coin/simons-cat.png", + "description": "인기 애니메이션 캐릭터 '사이먼의 고양이'를 기반으로 한 토큰입니다.", "collectionActive": false, "keywords": ["Animal", "NFT", "Fan-Driven"] }