Add channels
This commit is contained in:
parent
3f533d568a
commit
e40e2a3245
@ -7,7 +7,10 @@ import (
|
|||||||
var channelCmd = &cobra.Command{
|
var channelCmd = &cobra.Command{
|
||||||
Use: "channel",
|
Use: "channel",
|
||||||
Short: "Manage channels",
|
Short: "Manage channels",
|
||||||
Long: `
|
Long: `Manage channels.
|
||||||
|
|
||||||
|
A channel is a group of sources that can be viewed together. Adding a source
|
||||||
|
to a channel creates it and removing all sources from a channel deletes it.
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,19 +3,43 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/Jaculabilis/intake/core"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var channelAddCmd = &cobra.Command{
|
var channelAddCmd = &cobra.Command{
|
||||||
Use: "add",
|
Use: "add",
|
||||||
Short: "Create a channel",
|
Short: "Add a source to a channel",
|
||||||
Long: `
|
Long: `Add a source to a channel.
|
||||||
`,
|
`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
log.Fatal("not implemented")
|
channelAdd(stringArg(cmd, "channel"), stringArg(cmd, "source"))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
channelCmd.AddCommand(channelAddCmd)
|
channelCmd.AddCommand(channelAddCmd)
|
||||||
|
|
||||||
|
channelAddCmd.Flags().StringP("channel", "c", "", "Channel name")
|
||||||
|
channelAddCmd.MarkFlagRequired("channel")
|
||||||
|
|
||||||
|
channelAddCmd.Flags().StringP("source", "s", "", "Source to add")
|
||||||
|
channelAddCmd.MarkFlagRequired("source")
|
||||||
|
}
|
||||||
|
|
||||||
|
func channelAdd(channel string, source string) {
|
||||||
|
if channel == "" {
|
||||||
|
log.Fatal("error: --channel is empty")
|
||||||
|
}
|
||||||
|
if source == "" {
|
||||||
|
log.Fatal("error: --source is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := openAndMigrateDb()
|
||||||
|
|
||||||
|
if err := core.AddSourceToChannel(db, channel, source); err != nil {
|
||||||
|
log.Fatalf("error: failed to add source to channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Added source %s to channel %s", source, channel)
|
||||||
}
|
}
|
||||||
|
@ -3,19 +3,44 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/Jaculabilis/intake/core"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var channelDeleteCmd = &cobra.Command{
|
var channelDeleteCmd = &cobra.Command{
|
||||||
Use: "delete",
|
Use: "remove",
|
||||||
Short: "Delete a channel",
|
Aliases: []string{"rm"},
|
||||||
Long: `
|
Short: "Remove a source from a channel",
|
||||||
|
Long: `Remove a source from a channel.
|
||||||
`,
|
`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
log.Fatal("not implemented")
|
channelRemove(stringArg(cmd, "channel"), stringArg(cmd, "source"))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
channelCmd.AddCommand(channelDeleteCmd)
|
channelCmd.AddCommand(channelDeleteCmd)
|
||||||
|
|
||||||
|
channelDeleteCmd.Flags().StringP("channel", "c", "", "Channel name")
|
||||||
|
channelDeleteCmd.MarkFlagRequired("channel")
|
||||||
|
|
||||||
|
channelDeleteCmd.Flags().StringP("source", "s", "", "Source to add")
|
||||||
|
channelDeleteCmd.MarkFlagRequired("source")
|
||||||
|
}
|
||||||
|
|
||||||
|
func channelRemove(channel string, source string) {
|
||||||
|
if channel == "" {
|
||||||
|
log.Fatal("error: --channel is empty")
|
||||||
|
}
|
||||||
|
if source == "" {
|
||||||
|
log.Fatal("error: --source is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := openAndMigrateDb()
|
||||||
|
|
||||||
|
if err := core.DeleteSourceFromChannel(db, channel, source); err != nil {
|
||||||
|
log.Fatalf("error: failed to remove source from channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Removed source %s from channel %s", source, channel)
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var channelEditCmd = &cobra.Command{
|
|
||||||
Use: "edit",
|
|
||||||
Short: "Edit a channel",
|
|
||||||
Long: `
|
|
||||||
`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
log.Fatal("not implemented")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
channelCmd.AddCommand(channelEditCmd)
|
|
||||||
}
|
|
45
cmd/channelList.go
Normal file
45
cmd/channelList.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/Jaculabilis/intake/core"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var channelListCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "List channels",
|
||||||
|
Long: `List channels.
|
||||||
|
`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
channelList(stringArg(cmd, "channel"))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
channelCmd.AddCommand(channelListCmd)
|
||||||
|
|
||||||
|
channelListCmd.Flags().StringP("channel", "c", "", "List sources in a specific channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
func channelList(channel string) {
|
||||||
|
db := openAndMigrateDb()
|
||||||
|
|
||||||
|
channelSources, err := core.GetSourcesInChannel(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error: failed to get sources in channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel == "" {
|
||||||
|
for channel := range channelSources {
|
||||||
|
fmt.Println(channel)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, source := range channelSources[channel] {
|
||||||
|
fmt.Println(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
core/channel.go
Normal file
41
core/channel.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
func AddSourceToChannel(db DB, channel string, source string) error {
|
||||||
|
_, err := db.Exec(`
|
||||||
|
insert into channels (name, source)
|
||||||
|
values (?, ?)
|
||||||
|
`, channel, source)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteSourceFromChannel(db DB, channel string, source string) error {
|
||||||
|
_, err := db.Exec(`
|
||||||
|
delete from channels
|
||||||
|
where name = ? and source = ?
|
||||||
|
`, channel, source)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSourcesInChannel(db DB) (map[string][]string, error) {
|
||||||
|
rows, err := db.Query(`
|
||||||
|
select name, source
|
||||||
|
from channels
|
||||||
|
order by name, source
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
channelSources := make(map[string][]string)
|
||||||
|
for rows.Next() {
|
||||||
|
var name, source string
|
||||||
|
if err := rows.Scan(&name, &source); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
channelSources[name] = append(channelSources[name], source)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return channelSources, nil
|
||||||
|
}
|
70
core/channel_test.go
Normal file
70
core/channel_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestChannel(t *testing.T) {
|
||||||
|
db := EphemeralDb(t)
|
||||||
|
if err := AddSource(db, "one"); err != nil {
|
||||||
|
t.Fatalf("failed to add source: %v", err)
|
||||||
|
}
|
||||||
|
if err := AddSource(db, "two"); err != nil {
|
||||||
|
t.Fatalf("failed to add source: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sources to channel
|
||||||
|
if err := AddSourceToChannel(db, "channel", "one"); err != nil {
|
||||||
|
t.Fatalf("failed to add source to channel: %v", err)
|
||||||
|
}
|
||||||
|
if err := AddSourceToChannel(db, "channel", "two"); err != nil {
|
||||||
|
t.Fatalf("failed to add source to channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both sources are in the channel
|
||||||
|
sources, err := GetSourcesInChannel(db)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get sources in channel: %v", err)
|
||||||
|
}
|
||||||
|
if len(sources["channel"]) != 2 || sources["channel"][0] != "one" || sources["channel"][1] != "two" {
|
||||||
|
t.Fatalf("expected two sources, got %d: %v", len(sources), sources)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sources in channel after deletion
|
||||||
|
if err := DeleteSourceFromChannel(db, "channel", "one"); err != nil {
|
||||||
|
t.Fatalf("failed to delete source from channel: %v", err)
|
||||||
|
}
|
||||||
|
sources, err = GetSourcesInChannel(db)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get sources in channel: %v", err)
|
||||||
|
}
|
||||||
|
if len(sources) != 1 || sources["channel"][0] != "two" {
|
||||||
|
t.Fatalf("unexpected sources in channel after deletion: %v", sources)
|
||||||
|
}
|
||||||
|
if err := AddSourceToChannel(db, "channel", "one"); err != nil {
|
||||||
|
t.Fatalf("failed to add source to channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items on both sources appear in the channel
|
||||||
|
if err := AddItems(db, []Item{
|
||||||
|
{"one", "a", 0, true, "", "", "", "", 0, nil},
|
||||||
|
{"two", "b", 0, true, "", "", "", "", 0, nil},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("failed to add items to one: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := DeactivateItem(db, "one", "a"); err != nil {
|
||||||
|
t.Fatalf("failed to deactivate item: %v", err)
|
||||||
|
}
|
||||||
|
items, err := GetAllItemsForChannel(db, "channel")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get all items in channel: %v", err)
|
||||||
|
}
|
||||||
|
if len(items) != 2 || items[0].Id != "a" || items[1].Id != "b" {
|
||||||
|
t.Fatalf("expected two items, got %d: %v", len(items), items)
|
||||||
|
}
|
||||||
|
items, err = GetActiveItemsForChannel(db, "channel")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get all items in channel: %v", err)
|
||||||
|
}
|
||||||
|
if len(items) != 1 || items[0].Id != "b" {
|
||||||
|
t.Fatalf("expected one item, got %d: %v", len(items), items)
|
||||||
|
}
|
||||||
|
}
|
@ -202,3 +202,28 @@ func GetAllItemsForSource(db DB, source string) ([]Item, error) {
|
|||||||
order by case when time = 0 then created else time end, id
|
order by case when time = 0 then created else time end, id
|
||||||
`, source)
|
`, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetActiveItemsForChannel(db DB, channel string) ([]Item, error) {
|
||||||
|
return getItems(db, `
|
||||||
|
select
|
||||||
|
i.source, i.id, i.created, i.active, i.title, i.author, i.body, i.link, i.time, json(i.action)
|
||||||
|
from items i
|
||||||
|
join channels c on i.source = c.source
|
||||||
|
where
|
||||||
|
c.name = ?
|
||||||
|
and i.active <> 0
|
||||||
|
order by case when i.time = 0 then i.created else i.time end, i.id
|
||||||
|
`, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllItemsForChannel(db DB, channel string) ([]Item, error) {
|
||||||
|
return getItems(db, `
|
||||||
|
select
|
||||||
|
i.source, i.id, i.created, i.active, i.title, i.author, i.body, i.link, i.time, json(i.action)
|
||||||
|
from items i
|
||||||
|
join channels c on i.source = c.source
|
||||||
|
where
|
||||||
|
c.name = ?
|
||||||
|
order by case when i.time = 0 then i.created else i.time end, i.id
|
||||||
|
`, channel)
|
||||||
|
}
|
||||||
|
@ -31,3 +31,9 @@ create table items(
|
|||||||
primary key (source, id),
|
primary key (source, id),
|
||||||
foreign key (source) references sources (name) on delete cascade
|
foreign key (source) references sources (name) on delete cascade
|
||||||
) strict;
|
) strict;
|
||||||
|
create table channels(
|
||||||
|
name text not null,
|
||||||
|
source text not null,
|
||||||
|
unique (name, source) on conflict replace
|
||||||
|
foreign key (source) references sources (name) on delete cascade
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user