feat: add ability to interact with lists
Add the ability to create, update, list and delete lists. Note, adding accounts or removing them from lists is not scoped in this PR.
This commit is contained in:
parent
c38689fe28
commit
bc18c00c69
8 changed files with 485 additions and 15 deletions
75
cmd/enbas/create.go
Normal file
75
cmd/enbas/create.go
Normal 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 create")
|
||||
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
|
||||
}
|
65
cmd/enbas/delete.go
Normal file
65
cmd/enbas/delete.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
|
||||
)
|
||||
|
||||
type deleteCommand struct {
|
||||
*flag.FlagSet
|
||||
|
||||
resourceType string
|
||||
listID string
|
||||
}
|
||||
|
||||
func newDeleteCommand(name, summary string) *deleteCommand {
|
||||
command := deleteCommand{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
}
|
||||
|
||||
command.StringVar(&command.resourceType, "type", "", "specify the type of resource to delete")
|
||||
command.StringVar(&command.listID, "list-id", "", "specify the ID of the list to delete")
|
||||
|
||||
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
|
||||
|
||||
return &command
|
||||
}
|
||||
|
||||
func (c *deleteCommand) 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.deleteList,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[c.resourceType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported resource type %q", c.resourceType)
|
||||
}
|
||||
|
||||
return doFunc(gtsClient)
|
||||
}
|
||||
|
||||
func (c *deleteCommand) deleteList(gtsClient *client.Client) error {
|
||||
if c.listID == "" {
|
||||
return errors.New("the list-id flag is not set")
|
||||
}
|
||||
|
||||
if err := gtsClient.DeleteList(c.listID); err != nil {
|
||||
return fmt.Errorf("unable to delete the list; %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("The list was successfully deleted.")
|
||||
|
||||
return nil
|
||||
}
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
type Executor interface {
|
||||
Name() string
|
||||
Parse([]string) error
|
||||
Parse(args []string) error
|
||||
Execute() error
|
||||
}
|
||||
|
||||
|
@ -23,15 +23,21 @@ func run() error {
|
|||
const (
|
||||
login string = "login"
|
||||
version string = "version"
|
||||
show string = "show"
|
||||
showResource string = "show"
|
||||
switchAccount string = "switch"
|
||||
createResource string = "create"
|
||||
deleteResource string = "delete"
|
||||
updateResource string = "update"
|
||||
)
|
||||
|
||||
summaries := map[string]string{
|
||||
login: "login to an account on GoToSocial",
|
||||
version: "print the application's version and build information",
|
||||
show: "print details about a specified resource",
|
||||
showResource: "print details about a specified resource",
|
||||
switchAccount: "switch to an account",
|
||||
createResource: "create a specific resource",
|
||||
deleteResource: "delete a specific resource",
|
||||
updateResource: "update a specific resource",
|
||||
}
|
||||
|
||||
flag.Usage = enbasUsageFunc(summaries)
|
||||
|
@ -54,10 +60,16 @@ func run() error {
|
|||
executor = newLoginCommand(login, summaries[login])
|
||||
case version:
|
||||
executor = newVersionCommand(version, summaries[version])
|
||||
case show:
|
||||
executor = newShowCommand(show, summaries[show])
|
||||
case showResource:
|
||||
executor = newShowCommand(showResource, summaries[showResource])
|
||||
case switchAccount:
|
||||
executor = newSwitchCommand(switchAccount, summaries[switchAccount])
|
||||
case createResource:
|
||||
executor = newCreateCommand(createResource, summaries[createResource])
|
||||
case deleteResource:
|
||||
executor = newDeleteCommand(deleteResource, summaries[deleteResource])
|
||||
case updateResource:
|
||||
executor = newUpdateCommand(updateResource, summaries[updateResource])
|
||||
default:
|
||||
flag.Usage()
|
||||
return fmt.Errorf("unknown subcommand %q", subcommand)
|
||||
|
|
|
@ -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.GetAllLists()
|
||||
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
|
||||
}
|
||||
|
|
90
cmd/enbas/update.go
Normal file
90
cmd/enbas/update.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||
)
|
||||
|
||||
type updateCommand struct {
|
||||
*flag.FlagSet
|
||||
|
||||
resourceType string
|
||||
listID string
|
||||
listTitle string
|
||||
listRepliesPolicy string
|
||||
}
|
||||
|
||||
func newUpdateCommand(name, summary string) *updateCommand {
|
||||
command := updateCommand{
|
||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
||||
}
|
||||
|
||||
command.StringVar(&command.resourceType, "type", "", "specify the type of resource to update")
|
||||
command.StringVar(&command.listID, "list-id", "", "specify the ID of the list to update")
|
||||
command.StringVar(&command.listTitle, "list-title", "", "specify the title of the list")
|
||||
command.StringVar(&command.listRepliesPolicy, "list-replies-policy", "", "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 *updateCommand) 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.updateList,
|
||||
}
|
||||
|
||||
doFunc, ok := funcMap[c.resourceType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported resource type %q", c.resourceType)
|
||||
}
|
||||
|
||||
return doFunc(gtsClient)
|
||||
}
|
||||
|
||||
func (c *updateCommand) updateList(gtsClient *client.Client) error {
|
||||
if c.listID == "" {
|
||||
return errors.New("the list-id flag is not set")
|
||||
}
|
||||
|
||||
list, err := gtsClient.GetList(c.listID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get the list; %w", err)
|
||||
}
|
||||
|
||||
if c.listTitle != "" {
|
||||
list.Title = c.listTitle
|
||||
}
|
||||
|
||||
if c.listRepliesPolicy != "" {
|
||||
repliesPolicy, err := model.ParseListRepliesPolicy(c.listRepliesPolicy)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse the list replies policy; %w", err)
|
||||
}
|
||||
|
||||
list.RepliesPolicy = repliesPolicy
|
||||
}
|
||||
|
||||
updatedList, err := gtsClient.UpdateList(list)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update the list; %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Successfully updated the list.")
|
||||
fmt.Printf("\n%s\n", updatedList)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -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",
|
||||
|
|
108
internal/client/lists.go
Normal file
108
internal/client/lists.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
|
||||
)
|
||||
|
||||
const (
|
||||
listPath string = "/api/v1/lists"
|
||||
)
|
||||
|
||||
func (g *Client) GetAllLists() ([]model.List, error) {
|
||||
url := g.Authentication.Instance + listPath
|
||||
|
||||
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) GetList(listID string) (model.List, error) {
|
||||
url := g.Authentication.Instance + listPath + "/" + listID
|
||||
|
||||
var list model.List
|
||||
|
||||
if err := g.sendRequest(http.MethodGet, url, nil, &list); err != nil {
|
||||
return model.List{}, fmt.Errorf(
|
||||
"received an error after sending the request to get the list; %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return list, 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) UpdateList(listToUpdate model.List) (model.List, error) {
|
||||
params := struct {
|
||||
Title string `json:"title"`
|
||||
RepliesPolicy model.ListRepliesPolicy `json:"replies_policy"`
|
||||
}{
|
||||
Title: listToUpdate.Title,
|
||||
RepliesPolicy: listToUpdate.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 + listPath + "/" + listToUpdate.ID
|
||||
|
||||
var updatedList model.List
|
||||
|
||||
if err := g.sendRequest(http.MethodPut, url, requestBody, &updatedList); err != nil {
|
||||
return model.List{}, fmt.Errorf(
|
||||
"received an error after sending the request to update the list; %w",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return updatedList, 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
89
internal/model/list.go
Normal 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,
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue