-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add kakao channel, template, and brand template management #5
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
9b880d5
feat: add kakao channel, template, and brand template management comm…
Palbahngmiyine c2ec95f
fix: address review issues from pr-review-toolkit and codex
Palbahngmiyine ee0e9ab
fix: unify interface{} to any, reset pflag Changed state in tests
Palbahngmiyine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package cmd | ||
|
|
||
| import "github.com/spf13/cobra" | ||
|
|
||
| var kakaoCmd = &cobra.Command{ | ||
| Use: "kakao", | ||
| Short: "카카오톡 채널, 템플릿을 관리합니다", | ||
| } | ||
|
|
||
| func init() { | ||
| rootCmd.AddCommand(kakaoCmd) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package cmd | ||
|
|
||
| import "github.com/spf13/cobra" | ||
|
|
||
| var kakaoBrandTemplateCmd = &cobra.Command{ | ||
| Use: "brand-template", | ||
| Aliases: []string{"btpl"}, | ||
| Short: "카카오 브랜드 템플릿을 관리합니다", | ||
| } | ||
|
|
||
| func init() { | ||
| kakaoCmd.AddCommand(kakaoBrandTemplateCmd) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var ( | ||
| kakaoBtplCreateFlagChannelID string | ||
| kakaoBtplCreateFlagChannelGroupID string | ||
| kakaoBtplCreateFlagChatBubbleType string | ||
| kakaoBtplCreateFlagName string | ||
| kakaoBtplCreateFlagContent string | ||
| kakaoBtplCreateFlagAdult bool | ||
| kakaoBtplCreateFlagHeader string | ||
| kakaoBtplCreateFlagImageID string | ||
| kakaoBtplCreateFlagImageLink string | ||
| kakaoBtplCreateFlagAdditional string | ||
| kakaoBtplCreateFlagCarousel string | ||
| kakaoBtplCreateFlagMainWideItem string | ||
| kakaoBtplCreateFlagSubWideItemList string | ||
| kakaoBtplCreateFlagVideo string | ||
| kakaoBtplCreateFlagCommerce string | ||
| kakaoBtplCreateFlagButtons string | ||
| kakaoBtplCreateFlagCoupon string | ||
| ) | ||
|
|
||
| var kakaoBrandTemplateCreateCmd = &cobra.Command{ | ||
| Use: "create", | ||
| Short: "브랜드 템플릿을 생성합니다", | ||
| Long: `브랜드 템플릿을 생성합니다. 검수 없이 즉시 사용 가능합니다. | ||
|
|
||
| 사용법: | ||
| solactl kakao brand-template create --channel-id <channelId> --chat-bubble-type TEXT --content "내용" | ||
| solactl kakao brand-template create --channel-id <channelId> --chat-bubble-type IMAGE --content "내용" --image-id <imageId>`, | ||
| RunE: runKakaoBrandTemplateCreate, | ||
| } | ||
|
|
||
| func init() { | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagChannelID, "channel-id", "", "채널 ID (--channel-id 또는 --channel-group-id 필수)") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagChannelGroupID, "channel-group-id", "", "채널 그룹 ID") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagChatBubbleType, "chat-bubble-type", "", "말풍선 타입 (필수: TEXT/IMAGE/WIDE/WIDE_ITEM_LIST/CAROUSEL_FEED/PREMIUM_VIDEO/COMMERCE/CAROUSEL_COMMERCE)") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagName, "name", "", "이름") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagContent, "content", "", "내용") | ||
| kakaoBrandTemplateCreateCmd.Flags().BoolVar(&kakaoBtplCreateFlagAdult, "adult", false, "성인 콘텐츠") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagHeader, "header", "", "헤더") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagImageID, "image-id", "", "이미지 ID") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagImageLink, "image-link", "", "이미지 링크") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagAdditional, "additional-content", "", "추가 콘텐츠") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagCarousel, "carousel", "", "캐러셀 (JSON 객체)") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagMainWideItem, "main-wide-item", "", "메인 와이드 아이템 (JSON 객체)") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagSubWideItemList, "sub-wide-item-list", "", "서브 와이드 아이템 목록 (JSON 배열)") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagVideo, "video", "", "동영상 (JSON 객체)") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagCommerce, "commerce", "", "커머스 (JSON 객체)") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagButtons, "buttons", "", "버튼 목록 (JSON 배열)") | ||
| kakaoBrandTemplateCreateCmd.Flags().StringVar(&kakaoBtplCreateFlagCoupon, "coupon", "", "쿠폰 (JSON 객체)") | ||
| kakaoBrandTemplateCmd.AddCommand(kakaoBrandTemplateCreateCmd) | ||
| } | ||
|
|
||
| func runKakaoBrandTemplateCreate(cmd *cobra.Command, args []string) error { | ||
| // XOR validation | ||
| if kakaoBtplCreateFlagChannelID == "" && kakaoBtplCreateFlagChannelGroupID == "" { | ||
| return fmt.Errorf("--channel-id 또는 --channel-group-id를 입력하세요") | ||
| } | ||
| if kakaoBtplCreateFlagChannelID != "" && kakaoBtplCreateFlagChannelGroupID != "" { | ||
| return fmt.Errorf("--channel-id와 --channel-group-id는 동시에 사용할 수 없습니다") | ||
| } | ||
| if kakaoBtplCreateFlagChatBubbleType == "" { | ||
| return fmt.Errorf("말풍선 타입(--chat-bubble-type)을 입력하세요") | ||
| } | ||
|
|
||
| c, err := newClient() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| body := map[string]any{ | ||
| "chatBubbleType": kakaoBtplCreateFlagChatBubbleType, | ||
| } | ||
|
|
||
| // brand-template API uses pfId/pfGroupId directly | ||
| if kakaoBtplCreateFlagChannelID != "" { | ||
| body["pfId"] = kakaoBtplCreateFlagChannelID | ||
| } | ||
| if kakaoBtplCreateFlagChannelGroupID != "" { | ||
| body["pfGroupId"] = kakaoBtplCreateFlagChannelGroupID | ||
| } | ||
|
|
||
| if kakaoBtplCreateFlagName != "" { | ||
| body["name"] = kakaoBtplCreateFlagName | ||
| } | ||
| if kakaoBtplCreateFlagContent != "" { | ||
| body["content"] = kakaoBtplCreateFlagContent | ||
| } | ||
| if kakaoBtplCreateFlagAdult { | ||
| body["adult"] = true | ||
| } | ||
| if kakaoBtplCreateFlagHeader != "" { | ||
| body["header"] = kakaoBtplCreateFlagHeader | ||
| } | ||
| if kakaoBtplCreateFlagImageID != "" { | ||
| body["imageId"] = kakaoBtplCreateFlagImageID | ||
| } | ||
| if kakaoBtplCreateFlagImageLink != "" { | ||
| body["imageLink"] = kakaoBtplCreateFlagImageLink | ||
| } | ||
| if kakaoBtplCreateFlagAdditional != "" { | ||
| body["additionalContent"] = kakaoBtplCreateFlagAdditional | ||
| } | ||
|
|
||
| // JSON fields | ||
| if err := setJSONField(body, "carousel", kakaoBtplCreateFlagCarousel); err != nil { | ||
| return err | ||
| } | ||
| if err := setJSONField(body, "mainWideItem", kakaoBtplCreateFlagMainWideItem); err != nil { | ||
| return err | ||
| } | ||
| if err := setJSONField(body, "subWideItemList", kakaoBtplCreateFlagSubWideItemList); err != nil { | ||
| return err | ||
| } | ||
| if err := setJSONField(body, "video", kakaoBtplCreateFlagVideo); err != nil { | ||
| return err | ||
| } | ||
| if err := setJSONField(body, "commerce", kakaoBtplCreateFlagCommerce); err != nil { | ||
| return err | ||
| } | ||
| if err := setJSONField(body, "buttons", kakaoBtplCreateFlagButtons); err != nil { | ||
| return err | ||
| } | ||
| if err := setJSONField(body, "coupon", kakaoBtplCreateFlagCoupon); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| raw, err := c.Post(ctx(), "kakao/v2/brand-templates", body) | ||
| if err != nil { | ||
| return fmt.Errorf("브랜드 템플릿 생성 실패: %w", err) | ||
| } | ||
|
|
||
| p := printer() | ||
| if flagJSON { | ||
| return p.PrintJSON(raw) | ||
| } | ||
|
|
||
| _, _ = fmt.Fprintln(out(), "브랜드 템플릿이 생성되었습니다.") | ||
| return nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "fmt" | ||
| "os" | ||
| "strings" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| var kakaoBtplDeleteFlagYes bool | ||
|
|
||
| var kakaoBrandTemplateDeleteCmd = &cobra.Command{ | ||
| Use: "delete <brandTemplateId>", | ||
| Short: "브랜드 템플릿을 삭제합니다", | ||
| Long: `브랜드 템플릿을 삭제합니다. | ||
|
|
||
| 사용법: | ||
| solactl kakao brand-template delete KA01BP240000000000000000000000000 | ||
| solactl kakao brand-template delete KA01BP240000000000000000000000000 --yes`, | ||
| Args: cobra.ExactArgs(1), | ||
| RunE: runKakaoBrandTemplateDelete, | ||
| } | ||
|
|
||
| func init() { | ||
| kakaoBrandTemplateDeleteCmd.Flags().BoolVarP(&kakaoBtplDeleteFlagYes, "yes", "y", false, "확인 없이 삭제") | ||
| kakaoBrandTemplateCmd.AddCommand(kakaoBrandTemplateDeleteCmd) | ||
| } | ||
|
|
||
| func runKakaoBrandTemplateDelete(cmd *cobra.Command, args []string) error { | ||
| brandTemplateID := args[0] | ||
|
|
||
| if !kakaoBtplDeleteFlagYes { | ||
| _, _ = fmt.Fprintf(out(), "\u26a0 브랜드 템플릿 %s을 삭제하시겠습니까? (y/N): ", brandTemplateID) | ||
|
|
||
| reader := stdinReader | ||
| if reader == nil { | ||
| reader = bufio.NewReader(os.Stdin) | ||
| } | ||
| line, err := reader.ReadString('\n') | ||
| if err != nil { | ||
| return fmt.Errorf("입력을 읽을 수 없습니다: %w", err) | ||
| } | ||
| answer := strings.TrimSpace(strings.ToLower(line)) | ||
| if answer != "y" && answer != "yes" { | ||
| _, _ = fmt.Fprintln(out(), "삭제가 취소되었습니다.") | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| c, err := newClient() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| _, err = c.Delete(ctx(), fmt.Sprintf("kakao/v2/brand-templates/%s", brandTemplateID)) | ||
| if err != nil { | ||
| return fmt.Errorf("브랜드 템플릿 삭제 실패: %w", err) | ||
| } | ||
|
|
||
| _, _ = fmt.Fprintf(out(), "브랜드 템플릿 %s이 삭제되었습니다.\n", brandTemplateID) | ||
| return nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/url" | ||
| "strconv" | ||
|
|
||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/solapi/solactl/pkg/types" | ||
| ) | ||
|
|
||
| var ( | ||
| kakaoBtplListFlagName string | ||
| kakaoBtplListFlagChannelID string | ||
| kakaoBtplListFlagChannelGroupID string | ||
| kakaoBtplListFlagBrandTemplateID string | ||
| kakaoBtplListFlagChatBubbleType string | ||
| kakaoBtplListFlagStatus string | ||
| kakaoBtplListFlagStartKey string | ||
| kakaoBtplListFlagLimit int | ||
| ) | ||
|
|
||
| var kakaoBrandTemplateListCmd = &cobra.Command{ | ||
| Use: "list", | ||
| Short: "브랜드 템플릿 목록을 조회합니다", | ||
| Long: `브랜드 템플릿 목록을 조회합니다. | ||
|
|
||
| 사용법: | ||
| solactl kakao brand-template list | ||
| solactl kakao brand-template list --chat-bubble-type TEXT | ||
| solactl kakao brand-template list --start-key <nextKey>`, | ||
| RunE: runKakaoBrandTemplateList, | ||
| } | ||
|
|
||
| func init() { | ||
| kakaoBrandTemplateListCmd.Flags().StringVar(&kakaoBtplListFlagName, "name", "", "이름 필터") | ||
| kakaoBrandTemplateListCmd.Flags().StringVar(&kakaoBtplListFlagChannelID, "channel-id", "", "채널 ID 필터") | ||
| kakaoBrandTemplateListCmd.Flags().StringVar(&kakaoBtplListFlagChannelGroupID, "channel-group-id", "", "채널 그룹 ID 필터") | ||
| kakaoBrandTemplateListCmd.Flags().StringVar(&kakaoBtplListFlagBrandTemplateID, "brand-template-id", "", "브랜드 템플릿 ID 필터") | ||
| kakaoBrandTemplateListCmd.Flags().StringVar(&kakaoBtplListFlagChatBubbleType, "chat-bubble-type", "", "말풍선 타입 필터") | ||
| kakaoBrandTemplateListCmd.Flags().StringVar(&kakaoBtplListFlagStatus, "status", "", "상태 필터 (ACTIVE/INACTIVE/DELETED)") | ||
| kakaoBrandTemplateListCmd.Flags().StringVar(&kakaoBtplListFlagStartKey, "start-key", "", "페이지네이션 시작 키") | ||
| kakaoBrandTemplateListCmd.Flags().IntVar(&kakaoBtplListFlagLimit, "limit", 20, "조회 건수") | ||
| kakaoBrandTemplateCmd.AddCommand(kakaoBrandTemplateListCmd) | ||
| } | ||
|
|
||
| func runKakaoBrandTemplateList(cmd *cobra.Command, args []string) error { | ||
| c, err := newClient() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| params := url.Values{} | ||
| params.Set("limit", strconv.Itoa(kakaoBtplListFlagLimit)) | ||
| if kakaoBtplListFlagName != "" { | ||
| params.Set("name", kakaoBtplListFlagName) | ||
| } | ||
| // brand-template API uses pfId/pfGroupId directly | ||
| if kakaoBtplListFlagChannelID != "" { | ||
| params.Set("pfId", kakaoBtplListFlagChannelID) | ||
| } | ||
| if kakaoBtplListFlagChannelGroupID != "" { | ||
| params.Set("pfGroupId", kakaoBtplListFlagChannelGroupID) | ||
| } | ||
| if kakaoBtplListFlagBrandTemplateID != "" { | ||
| params.Set("brandTemplateId", kakaoBtplListFlagBrandTemplateID) | ||
| } | ||
| if kakaoBtplListFlagChatBubbleType != "" { | ||
| params.Set("chatBubbleType", kakaoBtplListFlagChatBubbleType) | ||
| } | ||
| if kakaoBtplListFlagStatus != "" { | ||
| params.Set("status", kakaoBtplListFlagStatus) | ||
| } | ||
| if kakaoBtplListFlagStartKey != "" { | ||
| params.Set("startKey", kakaoBtplListFlagStartKey) | ||
| } | ||
|
|
||
| raw, err := c.Get(ctx(), "kakao/v2/brand-templates", params) | ||
| if err != nil { | ||
| return fmt.Errorf("브랜드 템플릿 목록 조회 실패: %w", err) | ||
| } | ||
|
|
||
| p := printer() | ||
| if flagJSON { | ||
| return p.PrintJSON(raw) | ||
| } | ||
|
|
||
| var resp types.KakaoBrandTemplateListResponse | ||
| if err := json.Unmarshal(raw, &resp); err != nil { | ||
| return fmt.Errorf("응답 파싱 실패: %w", err) | ||
| } | ||
|
|
||
| headers := []string{"BRAND TEMPLATE ID", "NAME", "BUBBLE TYPE", "STATUS", "CREATED"} | ||
| rows := make([][]string, 0, len(resp.BrandTemplateList)) | ||
| for i := range resp.BrandTemplateList { | ||
| bt := &resp.BrandTemplateList[i] | ||
| rows = append(rows, []string{ | ||
| bt.BrandTemplateID, | ||
| bt.Name, | ||
| bt.ChatBubbleType, | ||
| types.DisplayStatus(bt.Status), | ||
| types.DisplayDate(bt.DateCreated), | ||
| }) | ||
| } | ||
| p.FormatTable(headers, rows) | ||
|
|
||
| if resp.NextKey != "" { | ||
| _, _ = fmt.Fprintf(errOut(), "\n다음 페이지: solactl kakao brand-template list --start-key %s\n", resp.NextKey) | ||
| } | ||
|
|
||
| return nil | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stdinReader 변수가 cmd 패키지 내의 비-테스트 파일 어디에도 정의되어 있지 않습니다. 이로 인해 컴파일 오류("undefined: stdinReader")가 발생합니다. 테스트 시 입력을 모킹하기 위한 용도라면 cmd/root.go 등에 패키지 수준 변수로 선언해야 합니다.