checkpoint: add ability to create and show lists

This commit is contained in:
Dan Anglin 2024-02-26 20:02:25 +00:00
parent f56e0d6e7c
commit 475d3ec038
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
6 changed files with 264 additions and 5 deletions

75
cmd/enbas/create.go Normal file
View file

@ -0,0 +1,75 @@
package main
import (
"errors"
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
)
type createCommand struct {
*flag.FlagSet
resourceType string
listTitle string
listRepliesPolicy string
}
func newCreateCommand(name, summary string) *createCommand {
command := createCommand{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
}
command.StringVar(&command.resourceType, "type", "", "specify the type of resource to display")
command.StringVar(&command.listTitle, "list-title", "", "specify the title of the list")
command.StringVar(&command.listRepliesPolicy, "list-replies-policy", "list", "specify the policy of the replies for this list (valid values are followed, list and none)")
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
return &command
}
func (c *createCommand) Execute() error {
if c.resourceType == "" {
return errors.New("the type field is not set")
}
gtsClient, err := client.NewClientFromConfig()
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client; %w", err)
}
funcMap := map[string]func(*client.Client) error{
"lists": c.createLists,
}
doFunc, ok := funcMap[c.resourceType]
if !ok {
return fmt.Errorf("unsupported type %q", c.resourceType)
}
return doFunc(gtsClient)
}
func (c *createCommand) createLists(gtsClient *client.Client) error {
if c.listTitle == "" {
return errors.New("the list-title flag is not set")
}
repliesPolicy, err := model.ParseListRepliesPolicy(c.listRepliesPolicy)
if err != nil {
return fmt.Errorf("unable to parse the list replies policy; %w", err)
}
list, err := gtsClient.CreateList(c.listTitle, repliesPolicy)
if err != nil {
return fmt.Errorf("unable to create the list; %w", err)
}
fmt.Println("Successfully created the following list:")
fmt.Printf("\n%s\n", list)
return nil
}

View file

@ -8,7 +8,7 @@ import (
type Executor interface {
Name() string
Parse([]string) error
Parse(args []string) error
Execute() error
}
@ -25,6 +25,7 @@ func run() error {
version string = "version"
show string = "show"
switchAccount string = "switch"
create string = "create"
)
summaries := map[string]string{
@ -32,6 +33,7 @@ func run() error {
version: "print the application's version and build information",
show: "print details about a specified resource",
switchAccount: "switch to an account",
create: "create a specific resource",
}
flag.Usage = enbasUsageFunc(summaries)
@ -58,6 +60,8 @@ func run() error {
executor = newShowCommand(show, summaries[show])
case switchAccount:
executor = newSwitchCommand(switchAccount, summaries[switchAccount])
case create:
executor = newCreateCommand(create, summaries[create])
default:
flag.Usage()
return fmt.Errorf("unknown subcommand %q", subcommand)

View file

@ -8,12 +8,13 @@ import (
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
type showCommand struct {
*flag.FlagSet
myAccount bool
targetType string
resourceType string
account string
statusID string
timelineType string
@ -28,7 +29,7 @@ func newShowCommand(name, summary string) *showCommand {
}
command.BoolVar(&command.myAccount, "my-account", false, "set to true to lookup your account")
command.StringVar(&command.targetType, "type", "", "specify the type of resource to display")
command.StringVar(&command.resourceType, "type", "", "specify the type of resource to display")
command.StringVar(&command.account, "account", "", "specify the account URI to lookup")
command.StringVar(&command.statusID, "status-id", "", "specify the ID of the status to display")
command.StringVar(&command.timelineType, "timeline-type", "home", "specify the type of timeline to display (valid values are home, public, list and tag)")
@ -41,6 +42,10 @@ func newShowCommand(name, summary string) *showCommand {
}
func (c *showCommand) Execute() error {
if c.resourceType == "" {
return errors.New("the type field is not set")
}
gtsClient, err := client.NewClientFromConfig()
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client; %w", err)
@ -51,11 +56,12 @@ func (c *showCommand) Execute() error {
"account": c.showAccount,
"status": c.showStatus,
"timeline": c.showTimeline,
"lists": c.showLists,
}
doFunc, ok := funcMap[c.targetType]
doFunc, ok := funcMap[c.resourceType]
if !ok {
return fmt.Errorf("unsupported type %q", c.targetType)
return fmt.Errorf("unsupported resource type %q", c.resourceType)
}
return doFunc(gtsClient)
@ -156,3 +162,24 @@ func (c *showCommand) showTimeline(gts *client.Client) error {
return nil
}
func (c *showCommand) showLists(gts *client.Client) error {
lists, err := gts.GetLists()
if err != nil {
return fmt.Errorf("unable to retrieve the lists; %w", err)
}
if len(lists) == 0 {
fmt.Println("You have no lists.")
return nil
}
fmt.Println(utilities.HeaderFormat("LISTS"))
for i := range lists {
fmt.Printf("\n%s\n", lists[i])
}
return nil
}

View file

@ -198,6 +198,10 @@ func (g *Client) sendRequest(method string, url string, requestBody io.Reader, o
)
}
if object == nil {
return nil
}
if err := json.NewDecoder(response.Body).Decode(object); err != nil {
return fmt.Errorf(
"unable to decode the response from the GoToSocial server; %w",

60
internal/client/lists.go Normal file
View file

@ -0,0 +1,60 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
)
func (g *Client) GetLists() ([]model.List, error) {
url := g.Authentication.Instance + "/api/v1/lists"
var lists []model.List
if err := g.sendRequest(http.MethodGet, url, nil, &lists); err != nil {
return nil, fmt.Errorf(
"received an error after sending the request to get the list of lists; %w",
err,
)
}
return lists, nil
}
func (g *Client) CreateList(title string, repliesPolicy model.ListRepliesPolicy) (model.List, error) {
params := struct {
Title string `json:"title"`
RepliesPolicy model.ListRepliesPolicy `json:"replies_policy"`
}{
Title: title,
RepliesPolicy: repliesPolicy,
}
data, err := json.Marshal(params)
if err != nil {
return model.List{}, fmt.Errorf("unable to marshal the request body; %w", err)
}
requestBody := bytes.NewBuffer(data)
url := g.Authentication.Instance + "/api/v1/lists"
var list model.List
if err := g.sendRequest(http.MethodPost, url, requestBody, &list); err != nil {
return model.List{}, fmt.Errorf(
"received an error after sending the request to create the list; %w",
err,
)
}
return list, nil
}
func (g *Client) DeleteList(listID string) error {
url := g.Authentication.Instance + "/api/v1/lists/" + listID
return g.sendRequest(http.MethodDelete, url, nil, nil)
}

89
internal/model/list.go Normal file
View file

@ -0,0 +1,89 @@
package model
import (
"encoding/json"
"errors"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
type ListRepliesPolicy int
const (
ListRepliesPolicyFollowed ListRepliesPolicy = iota
ListRepliesPolicyList
ListRepliesPolicyNone
)
func ParseListRepliesPolicy(policy string) (ListRepliesPolicy, error) {
switch policy {
case "followed":
return ListRepliesPolicyFollowed, nil
case "list":
return ListRepliesPolicyList, nil
case "none":
return ListRepliesPolicyNone, nil
}
return ListRepliesPolicy(-1), errors.New("invalid list replies policy")
}
func (l ListRepliesPolicy) String() string {
switch l {
case ListRepliesPolicyFollowed:
return "followed"
case ListRepliesPolicyList:
return "list"
case ListRepliesPolicyNone:
return "none"
}
return ""
}
func (l ListRepliesPolicy) MarshalJSON() ([]byte, error) {
str := l.String()
if str == "" {
return nil, errors.New("invalid list replies policy")
}
return json.Marshal(str)
}
func (l *ListRepliesPolicy) UnmarshalJSON(data []byte) error {
var (
value string
err error
)
if err = json.Unmarshal(data, &value); err != nil {
return fmt.Errorf("unable to unmarshal the data; %w", err)
}
*l, err = ParseListRepliesPolicy(value)
if err != nil {
return fmt.Errorf("unable to parse %s as a list replies policy; %w", value, err)
}
return nil
}
type List struct {
ID string `json:"id"`
RepliesPolicy ListRepliesPolicy `json:"replies_policy"`
Title string `json:"title"`
}
func (l List) String() string {
format := `%s %s
%s %s
%s %s`
return fmt.Sprintf(
format,
utilities.FieldFormat("List ID:"), l.ID,
utilities.FieldFormat("Title:"), l.Title,
utilities.FieldFormat("Replies Policy:"), l.RepliesPolicy,
)
}