commit 2d8f34ace2c0d0ed73f8a0cf5cde8fe1a307bce8 Author: Akhil Gupta Date: Mon Oct 5 17:54:46 2020 +0530 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92a3977 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +*.db + +# Dependency directories (remove the comment below to include it) +# vendor/ +assets/* +keys/* \ No newline at end of file diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..eeb23e5 --- /dev/null +++ b/client/index.html @@ -0,0 +1,17 @@ + + + + + + PodGrab + + +

{{ .title }}

+ +
    + {{range .podcasts}} +
  1. {{.Title}} +
  2. {{end}} +
+ + \ No newline at end of file diff --git a/controllers/podcast.go b/controllers/podcast.go new file mode 100644 index 0000000..773922b --- /dev/null +++ b/controllers/podcast.go @@ -0,0 +1,95 @@ +package controllers + +import ( + "fmt" + "net/http" + + "github.com/akhilrex/podgrab/service" + + "github.com/akhilrex/podgrab/db" + "github.com/gin-gonic/gin" +) + +type SearchQuery struct { + Q string `binding:"required" form:"q"` + Type string `form:"type"` +} + +type SearchByIdQuery struct { + Id string `binding:"required" uri:"id" json:"id" form:"id"` +} + +type AddPodcastData struct { + url string `binding:"required" form:"url" json:"url"` +} + +func GetAllPodcasts(c *gin.Context) { + var podcasts []db.Podcast + db.GetAllPodcasts(&podcasts) + c.JSON(200, podcasts) +} + +func GetPodcastById(c *gin.Context) { + var searchByIdQuery SearchByIdQuery + + if c.ShouldBindUri(&searchByIdQuery) == nil { + + var podcast db.Podcast + + err := db.GetPodcastById(searchByIdQuery.Id, &podcast) + fmt.Println(err) + c.JSON(200, podcast) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + } +} + +func GetPodcastItemsByPodcastId(c *gin.Context) { + var searchByIdQuery SearchByIdQuery + + if c.ShouldBindUri(&searchByIdQuery) == nil { + + var podcastItems []db.PodcastItem + + err := db.GetAllPodcastItemsByPodcastId(searchByIdQuery.Id, &podcastItems) + fmt.Println(err) + c.JSON(200, podcastItems) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + } +} + +func GetAllPodcastItems(c *gin.Context) { + var podcasts []db.PodcastItem + db.GetAllPodcastItems(&podcasts) + c.JSON(200, podcasts) +} + +func GetPodcastItemById(c *gin.Context) { + var searchByIdQuery SearchByIdQuery + + if c.ShouldBindUri(&searchByIdQuery) == nil { + + var podcast db.PodcastItem + + err := db.GetPodcastItemById(searchByIdQuery.Id, &podcast) + fmt.Println(err) + c.JSON(200, podcast) + } else { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"}) + } +} + +func AddPodcast(c *gin.Context) { + var addPodcastData AddPodcastData + err := c.ShouldBindJSON(&addPodcastData) + if err == nil { + + service.AddPodcast(addPodcastData.url) + // fmt.Println(time.Unix(addPodcastData.StartDate, 0)) + c.JSON(200, addPodcastData) + } else { + fmt.Println(err.Error()) + c.JSON(http.StatusBadRequest, err) + } +} diff --git a/db/base.go b/db/base.go new file mode 100644 index 0000000..4d1d79e --- /dev/null +++ b/db/base.go @@ -0,0 +1,22 @@ +package db + +import ( + "time" + + uuid "github.com/satori/go.uuid" + "gorm.io/gorm" +) + +//Base is +type Base struct { + ID string `sql:"type:uuid;primary_key"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt *time.Time `gorm:"index"` +} + +//BeforeCreate +func (base *Base) BeforeCreate(tx *gorm.DB) error { + tx.Statement.SetColumn("ID", uuid.NewV4().String()) + return nil +} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..1c35c66 --- /dev/null +++ b/db/db.go @@ -0,0 +1,39 @@ +package db + +import ( + "fmt" + + "gorm.io/driver/sqlite" + + "gorm.io/gorm" +) + +//DB is +var DB *gorm.DB + +//Init is used to Initialize Database +func Init() (*gorm.DB, error) { + // github.com/mattn/go-sqlite3 + db, err := gorm.Open(sqlite.Open("podgrab.db"), &gorm.Config{}) + if err != nil { + fmt.Println("db err: ", err) + return nil, err + } + + localDB, _ := db.DB() + localDB.SetMaxIdleConns(10) + //db.LogMode(true) + DB = db + return DB, nil +} + +//Migrate Database +func Migrate() { + DB.AutoMigrate(&Podcast{}, &PodcastItem{}) + +} + +// Using this function to get a connection, you can create your connection pool here. +func GetDB() *gorm.DB { + return DB +} diff --git a/db/dbfunctions.go b/db/dbfunctions.go new file mode 100644 index 0000000..25d8d3b --- /dev/null +++ b/db/dbfunctions.go @@ -0,0 +1,70 @@ +package db + +import ( + "time" + + "gorm.io/gorm/clause" +) + +func GetPodcastByURL(url string, podcast *Podcast) error { + result := DB.Preload(clause.Associations).Where(&Podcast{URL: url}).First(&podcast) + return result.Error +} +func GetAllPodcasts(podcasts *[]Podcast) error { + + result := DB.Preload("PodcastItems").Find(&podcasts) + return result.Error +} +func GetAllPodcastItems(podcasts *[]PodcastItem) error { + + result := DB.Preload("Podcast").Find(&podcasts) + return result.Error +} +func GetPodcastById(id string, podcast *Podcast) error { + + result := DB.Preload(clause.Associations).First(&podcast, "id=?", id) + return result.Error +} + +func GetPodcastItemById(id string, podcastItem *PodcastItem) error { + + result := DB.Preload(clause.Associations).First(&podcastItem, "id=?", id) + return result.Error +} + +func GetAllPodcastItemsByPodcastId(podcastId string, podcasts *[]PodcastItem) error { + + result := DB.Preload(clause.Associations).Where(&PodcastItem{PodcastID: podcastId}).Find(&podcasts) + return result.Error +} + +func GetAllPodcastItemsToBeDownloaded() (*[]PodcastItem, error) { + var podcastItems []PodcastItem + result := DB.Preload(clause.Associations).Where(&PodcastItem{DownloadDate: time.Time{}}).Find(&podcastItems) + return &podcastItems, result.Error +} + +func GetPodcastItemByPodcastIdAndGUID(podcastId string, guid string, podcastItem *PodcastItem) error { + + result := DB.Preload(clause.Associations).Where(&PodcastItem{PodcastID: podcastId, GUID: guid}).First(&podcastItem) + return result.Error +} +func GetPodcastByTitleAndAuthor(title string, author string, podcast *Podcast) error { + + result := DB.Preload(clause.Associations).Where(&Podcast{Title: title, Author: author}).First(&podcast) + return result.Error +} + +func CreatePodcast(podcast *Podcast) error { + tx := DB.Create(&podcast) + return tx.Error +} + +func CreatePodcastItem(podcastItem *PodcastItem) error { + tx := DB.Create(&podcastItem) + return tx.Error +} +func UpdatePodcastItem(podcastItem *PodcastItem) error { + tx := DB.Save(&podcastItem) + return tx.Error +} diff --git a/db/podcast.go b/db/podcast.go new file mode 100644 index 0000000..579af07 --- /dev/null +++ b/db/podcast.go @@ -0,0 +1,43 @@ +package db + +import ( + "time" +) + +//Podcast is +type Podcast struct { + Base + Title string + + Summary string `gorm:"type:text"` + + Author string + + Image string + + URL string + + PodcastItems []PodcastItem +} + +//PodcastItem is +type PodcastItem struct { + Base + PodcastID string + Podcast Podcast + Title string + Summary string `gorm:"type:text"` + + EpisodeType string + + Duration int + + PubDate time.Time + + FileURL string + + GUID string + + DownloadDate time.Time + DownloadPath string +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..600e2aa --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/akhilrex/podgrab + +go 1.15 + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/gin-gonic/gin v1.6.3 + github.com/gobeam/stringy v0.0.0-20200717095810-8a3637503f62 + github.com/jasonlvhit/gocron v0.0.1 + github.com/satori/go.uuid v1.2.0 + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a + gorm.io/driver/sqlite v1.1.3 + gorm.io/gorm v1.20.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..562d096 --- /dev/null +++ b/go.sum @@ -0,0 +1,102 @@ +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gobeam/stringy v0.0.0-20200717095810-8a3637503f62 h1:5yOhT/D6x5qqfcRnR98bvpGlduR4Shkj8Jat+SalYpQ= +github.com/gobeam/stringy v0.0.0-20200717095810-8a3637503f62/go.mod h1:W3620X9dJHf2FSZF5fRnWekHcHQjwmCz8ZQ2d1qloqE= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jasonlvhit/gocron v0.0.1 h1:qTt5qF3b3srDjeOIR4Le1LfeyvoYzJlYpqvG7tJX5YU= +github.com/jasonlvhit/gocron v0.0.1/go.mod h1:k9a3TV8VcU73XZxfVHCHWMWF9SOqgoku0/QlY2yvlA4= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA= +github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gorm.io/driver/sqlite v1.1.3 h1:BYfdVuZB5He/u9dt4qDpZqiqDJ6KhPqs5QUqsr/Eeuc= +gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c= +gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.2 h1:bZzSEnq7NDGsrd+n3evOOedDrY5oLM5QPlCjZJUK2ro= +gorm.io/gorm v1.20.2/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= diff --git a/main.go b/main.go new file mode 100644 index 0000000..12950b4 --- /dev/null +++ b/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/akhilrex/podgrab/controllers" + "github.com/akhilrex/podgrab/db" + "github.com/akhilrex/podgrab/service" + "github.com/gin-gonic/gin" + "github.com/jasonlvhit/gocron" +) + +func main() { + + // os.Remove("./podgrab.db") + + var err error + db.DB, err = db.Init() + if err != nil { + fmt.Println("statuse: ", err) + } + db.Migrate() + + r := gin.Default() + r.Static("/assets", "./assets") + r.LoadHTMLGlob("client/*") + + r.GET("/podcasts", controllers.AddPodcast) + r.POST("/podcasts", controllers.GetAllPodcasts) + r.GET("/podcasts/:id", controllers.GetPodcastById) + r.GET("/podcasts/:id/items", controllers.GetPodcastItemsByPodcastId) + + r.GET("/podcastitems", controllers.GetAllPodcastItems) + r.GET("/podcastitems/:id", controllers.GetPodcastItemById) + + r.GET("/ping", func(c *gin.Context) { + + data, err := service.AddPodcast(c.Query("url")) + go service.AddPodcastItems(&data) + //data, err := db.GetAllPodcastItemsToBeDownloaded() + if err == nil { + c.JSON(200, data) + } else { + c.JSON(http.StatusInternalServerError, err.Error()) + } + }) + r.GET("/pong", func(c *gin.Context) { + + data, err := db.GetAllPodcastItemsToBeDownloaded() + + for _, item := range *data { + url, _ := service.Download(item.FileURL, item.Title, item.Podcast.Title) + service.SetPodcastItemAsDownloaded(item.ID, url) + } + + if err == nil { + c.JSON(200, data) + } else { + c.JSON(http.StatusInternalServerError, err.Error()) + } + }) + + r.GET("/", func(c *gin.Context) { + //var podcasts []db.Podcast + podcasts := service.GetAllPodcasts() + c.HTML(http.StatusOK, "index.html", gin.H{"title": "Main website", "podcasts": podcasts}) + }) + + go intiCron() + + r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") + +} + +func intiCron() { + gocron.Every(20).Minutes().Do(service.DownloadMissingEpisodes) + gocron.Every(20).Minutes().Do(service.RefreshEpisodes) + <-gocron.Start() +} diff --git a/service/fileService.go b/service/fileService.go new file mode 100644 index 0000000..ade79f3 --- /dev/null +++ b/service/fileService.go @@ -0,0 +1,79 @@ +package service + +import ( + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + + stringy "github.com/gobeam/stringy" +) + +func Download(link string, episodeTitle string, podcastName string) (string, error) { + client := httpClient() + resp, err := client.Get(link) + if err != nil { + return "", err + } + + fileName := getFileName(link, episodeTitle, ".mp3") + folder := createIfFoldeDoesntExist(podcastName) + finalPath := path.Join(folder, fileName) + file, err := os.Create(finalPath) + if err != nil { + return "", err + } + defer resp.Body.Close() + _, erra := io.Copy(file, resp.Body) + //fmt.Println(size) + defer file.Close() + if erra != nil { + return "", erra + } + return finalPath, nil + +} +func httpClient() *http.Client { + client := http.Client{ + CheckRedirect: func(r *http.Request, via []*http.Request) error { + r.URL.Opaque = r.URL.Path + return nil + }, + } + + return &client +} + +func createIfFoldeDoesntExist(folder string) string { + str := stringy.New(folder) + folder = str.RemoveSpecialCharacter() + folderPath := path.Join("./assets/", folder) + if _, err := os.Stat(folderPath); os.IsNotExist(err) { + os.MkdirAll(folderPath, 0755) + } + return folderPath +} + +func getFileName(link string, title string, defaultExtension string) string { + fileUrl, err := url.Parse(link) + checkError(err) + + parsed := fileUrl.Path + ext := filepath.Ext(parsed) + + if len(ext) == 0 { + ext = defaultExtension + } + str := stringy.New(title) + str = stringy.New(str.RemoveSpecialCharacter()) + return str.KebabCase().Get() + ext + +} + +func checkError(err error) { + if err != nil { + panic(err) + } +} diff --git a/service/podcastModels.go b/service/podcastModels.go new file mode 100644 index 0000000..f2eee12 --- /dev/null +++ b/service/podcastModels.go @@ -0,0 +1,93 @@ +package service + +import "encoding/xml" + +//PodcastData is +type PodcastData struct { + XMLName xml.Name `xml:"rss"` + Text string `xml:",chardata"` + Itunes string `xml:"itunes,attr"` + Atom string `xml:"atom,attr"` + Media string `xml:"media,attr"` + Psc string `xml:"psc,attr"` + Omny string `xml:"omny,attr"` + Content string `xml:"content,attr"` + Googleplay string `xml:"googleplay,attr"` + Acast string `xml:"acast,attr"` + Version string `xml:"version,attr"` + Channel struct { + Text string `xml:",chardata"` + Language string `xml:"language"` + Link []struct { + Text string `xml:",chardata"` + Rel string `xml:"rel,attr"` + Type string `xml:"type,attr"` + Href string `xml:"href,attr"` + } `xml:"link"` + Title string `xml:"title"` + Description string `xml:"description"` + Type string `xml:"type"` + Summary string `xml:"summary"` + Owner struct { + Text string `xml:",chardata"` + Name string `xml:"name"` + Email string `xml:"email"` + } `xml:"owner"` + Author string `xml:"author"` + Copyright string `xml:"copyright"` + Explicit string `xml:"explicit"` + Category struct { + Text string `xml:",chardata"` + AttrText string `xml:"text,attr"` + Category struct { + Text string `xml:",chardata"` + AttrText string `xml:"text,attr"` + } `xml:"category"` + } `xml:"category"` + Image struct { + Text string `xml:",chardata"` + Href string `xml:"href,attr"` + URL string `xml:"url"` + Title string `xml:"title"` + Link string `xml:"link"` + } `xml:"image"` + Item []struct { + Text string `xml:",chardata"` + Title string `xml:"title"` + Description string `xml:"description"` + Encoded string `xml:"encoded"` + Summary string `xml:"summary"` + EpisodeType string `xml:"episodeType"` + Author string `xml:"author"` + Image struct { + Text string `xml:",chardata"` + Href string `xml:"href,attr"` + } `xml:"image"` + Content []struct { + Text string `xml:",chardata"` + URL string `xml:"url,attr"` + Type string `xml:"type,attr"` + Player struct { + Text string `xml:",chardata"` + URL string `xml:"url,attr"` + } `xml:"player"` + } `xml:"content"` + Guid struct { + Text string `xml:",chardata"` + IsPermaLink string `xml:"isPermaLink,attr"` + } `xml:"guid"` + ClipId string `xml:"clipId"` + PubDate string `xml:"pubDate"` + Duration string `xml:"duration"` + Enclosure struct { + Text string `xml:",chardata"` + URL string `xml:"url,attr"` + Length string `xml:"length,attr"` + Type string `xml:"type,attr"` + } `xml:"enclosure"` + Link string `xml:"link"` + StitcherId string `xml:"stitcherId"` + Episode string `xml:"episode"` + } `xml:"item"` + } `xml:"channel"` +} diff --git a/service/podcastService.go b/service/podcastService.go new file mode 100644 index 0000000..3c6b636 --- /dev/null +++ b/service/podcastService.go @@ -0,0 +1,143 @@ +package service + +import ( + "encoding/xml" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "strconv" + "time" + + "github.com/akhilrex/podgrab/db" + "gorm.io/gorm" +) + +//Fetch is +func FetchURL(url string) (PodcastData, error) { + body := makeQuery(url) + var response PodcastData + err := xml.Unmarshal(body, &response) + return response, err +} +func GetAllPodcasts() *[]db.Podcast { + var podcasts []db.Podcast + db.GetAllPodcasts(&podcasts) + return &podcasts +} +func AddPodcast(url string) (db.Podcast, error) { + + data, err := FetchURL(url) + if err != nil { + fmt.Println("Error") + log.Fatal(err) + return db.Podcast{}, err + } + var podcast db.Podcast + err = db.GetPodcastByTitleAndAuthor(data.Channel.Title, data.Channel.Author, &podcast) + if errors.Is(err, gorm.ErrRecordNotFound) { + podcast := db.Podcast{ + Title: data.Channel.Title, + Summary: data.Channel.Summary, + Author: data.Channel.Author, + Image: data.Channel.Image.URL, + URL: url, + } + err = db.CreatePodcast(&podcast) + return podcast, err + } + return podcast, err + +} + +func AddPodcastItems(podcast *db.Podcast) error { + fmt.Println("Creating: " + podcast.ID) + data, err := FetchURL(podcast.URL) + if err != nil { + log.Fatal(err) + return err + } + + for i := 0; i < 5; i++ { + obj := data.Channel.Item[i] + var podcastItem db.PodcastItem + err := db.GetPodcastItemByPodcastIdAndGUID(podcast.ID, obj.Guid.Text, &podcastItem) + if errors.Is(err, gorm.ErrRecordNotFound) { + duration, _ := strconv.Atoi(obj.Duration) + pubDate, _ := time.Parse(time.RFC1123Z, obj.PubDate) + podcastItem = db.PodcastItem{ + PodcastID: podcast.ID, + Title: obj.Title, + Summary: obj.Summary, + EpisodeType: obj.EpisodeType, + Duration: duration, + PubDate: pubDate, + FileURL: obj.Enclosure.URL, + GUID: obj.Guid.Text, + } + db.CreatePodcastItem(&podcastItem) + } + } + return err +} + +func SetPodcastItemAsDownloaded(id string, location string) { + var podcastItem db.PodcastItem + db.GetPodcastItemById(id, &podcastItem) + + podcastItem.DownloadDate = time.Now() + podcastItem.DownloadPath = location + + db.UpdatePodcastItem(&podcastItem) +} + +func DownloadMissingEpisodes() error { + data, err := db.GetAllPodcastItemsToBeDownloaded() + + fmt.Println("Processing episodes: %d", len(*data)) + if err != nil { + return err + } + for _, item := range *data { + url, _ := Download(item.FileURL, item.Title, item.Podcast.Title) + SetPodcastItemAsDownloaded(item.ID, url) + } + return nil +} + +func RefreshEpisodes() error { + var data []db.Podcast + err := db.GetAllPodcasts(&data) + + if err != nil { + return err + } + for _, item := range data { + AddPodcastItems(&item) + + } + return nil +} + +func makeQuery(url string) []byte { + //link := "https://www.goodreads.com/search/index.xml?q=Good%27s+Omens&key=" + "jCmNlIXjz29GoB8wYsrd0w" + //link := "https://www.goodreads.com/search/index.xml?key=jCmNlIXjz29GoB8wYsrd0w&q=Ender%27s+Game" + //fmt.Println(url) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Fatal(err) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + + defer resp.Body.Close() + fmt.Println("Response status:", resp.Status) + body, err := ioutil.ReadAll(resp.Body) + + return body + +}