├── hack └── docker │ ├── s6 │ ├── .s6-svscan │ │ └── finish │ └── backpulse-serve │ │ ├── setup │ │ └── run │ ├── nsswitch.conf │ ├── finalize.sh │ └── start.sh ├── .gitignore ├── config.json.template ├── utils ├── stripe.go ├── jwt.go ├── utils.go ├── modules.go ├── sites.go ├── strings.go ├── config.go ├── users.go ├── response.go └── googlecloud.go ├── models ├── Translation.go ├── Config.go ├── EmailVerification.go ├── AboutContent.go ├── File.go ├── Article.go ├── VideoGroup.go ├── Project.go ├── Album.go ├── ContactContent.go ├── Track.go ├── Video.go ├── Site.go ├── Gallery.go ├── User.go └── Photo.go ├── routes ├── admin │ ├── constants.go │ ├── about.go │ ├── contact.go │ ├── projects.go │ ├── files.go │ ├── articles.go │ ├── tracks.go │ ├── videos.go │ ├── albums.go │ ├── videogroups.go │ ├── photos.go │ ├── users.go │ ├── galleries.go │ ├── admin.go │ └── sites.go ├── routes.go └── client │ ├── client.go │ └── documentation.md ├── handlers ├── admin │ ├── constants.go │ ├── about.go │ ├── contact.go │ ├── projects.go │ ├── articles.go │ ├── files.go │ ├── tracks.go │ ├── videogroups.go │ ├── albums.go │ ├── videos.go │ ├── galleries.go │ ├── photos.go │ ├── users.go │ └── sites.go └── client │ ├── about.go │ ├── contact.go │ ├── articles.go │ ├── projects.go │ ├── albums.go │ ├── videos.go │ └── galleries.go ├── .gitlab-ci.yml ├── constants ├── site_modules.go └── languages.go ├── .env ├── database ├── about.go ├── contact.go ├── files.go ├── database.go ├── articles.go ├── videos.go ├── projects.go ├── tracks.go ├── albums.go ├── videogroups.go ├── photos.go ├── galleries.go ├── users.go └── sites.go ├── go.mod ├── LICENSE ├── Dockerfile ├── main.go ├── strings.json ├── Makefile ├── README.md └── CODE_OF_CONDUCT.md /hack/docker/s6/.s6-svscan/finish: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Cleanup some services and s6 event folder 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .idea 3 | *.exe 4 | backpulse 5 | config.json 6 | backpulse.json 7 | google_credentials.json -------------------------------------------------------------------------------- /hack/docker/s6/backpulse-serve/setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd /app/backpulse 4 | 5 | # Link volumed data with app data 6 | ln -sfn /data/log log 7 | -------------------------------------------------------------------------------- /hack/docker/s6/backpulse-serve/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test -f ./setup; then 4 | source ./setup 5 | fi 6 | 7 | export USER=backpulse 8 | exec gosu $USER /app/backpulse/backpulse -env /app/backpulse/.env 9 | -------------------------------------------------------------------------------- /config.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "URI": "mongodb://...", 3 | "Database": "-", 4 | "Secret": "-", 5 | "GmailAddress": "-", 6 | "GmailPassword": "-", 7 | "StripeKey": "-", 8 | "BucketName": "-" 9 | } -------------------------------------------------------------------------------- /utils/stripe.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/stripe/stripe-go/client" 5 | ) 6 | 7 | var StripeClient client.API 8 | 9 | func InitStripe() { 10 | config := GetConfig() 11 | StripeClient.Init(config.StripeKey, nil) 12 | } 13 | -------------------------------------------------------------------------------- /models/Translation.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Translation struct 4 | type Translation struct { 5 | LanguageName string `json:"language_name" bson:"language_name"` 6 | LanguageCode string `json:"language_code" bson:"language_code"` 7 | Content string `json:"content" bson:"content"` 8 | } 9 | -------------------------------------------------------------------------------- /routes/admin/constants.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleConstants(r *mux.Router) { 9 | r.HandleFunc("/constants/languages", adminHandlers.GetLanguages).Methods("GET") 10 | } 11 | -------------------------------------------------------------------------------- /utils/jwt.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/dgrijalva/jwt-go" 7 | ) 8 | 9 | //GetDecodedJWT Returns decoded JWT object 10 | func GetDecodedJWT(r *http.Request) jwt.MapClaims { 11 | return r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims) 12 | } 13 | -------------------------------------------------------------------------------- /models/Config.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | //Config config struct 4 | type Config struct { 5 | URI string 6 | Database string 7 | Secret string 8 | GmailAddress string 9 | GmailPassword string 10 | StripeKey string 11 | BucketName string 12 | BucketPubURL string 13 | } 14 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | // GetSubdomain : return subdomain of request 9 | func GetSubdomain(r *http.Request) string { 10 | fullHost := r.Host 11 | splitHost := strings.Split(fullHost, ".") 12 | if len(splitHost) < 1 { 13 | return "" 14 | } 15 | return splitHost[0] 16 | } 17 | -------------------------------------------------------------------------------- /hack/docker/nsswitch.conf: -------------------------------------------------------------------------------- 1 | # /etc/nsswitch.conf 2 | 3 | passwd: compat 4 | group: compat 5 | shadow: compat 6 | 7 | hosts: files dns 8 | networks: files 9 | 10 | protocols: db files 11 | services: db files 12 | ethers: db files 13 | rpc: db files 14 | 15 | netgroup: nis 16 | 17 | -------------------------------------------------------------------------------- /routes/admin/about.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleAbout(r *mux.Router) { 9 | r.Handle("/about/{name}", ProtectedRoute(adminHandlers.GetAbout)).Methods("GET") 10 | r.Handle("/about/{name}", ProtectedRoute(adminHandlers.UpdateAbout)).Methods("PUT") 11 | } 12 | -------------------------------------------------------------------------------- /utils/modules.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/backpulse/core/constants" 5 | ) 6 | 7 | // CheckModuleExists : loop through list of modules to check if it included 8 | func CheckModuleExists(module string) bool { 9 | for _, m := range constants.Modules { 10 | if constants.Module(module) == m { 11 | return true 12 | } 13 | } 14 | return false 15 | } 16 | -------------------------------------------------------------------------------- /handlers/admin/constants.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/backpulse/core/constants" 7 | "github.com/backpulse/core/utils" 8 | ) 9 | 10 | // GetLanguages : return array of languages 11 | func GetLanguages(w http.ResponseWriter, r *http.Request) { 12 | utils.RespondWithJSON(w, http.StatusOK, "success", constants.Languages) 13 | } 14 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - deploy 3 | 4 | production: 5 | stage: deploy 6 | script: 7 | - apt-get update -qy 8 | - apt-get install -y ruby-dev 9 | - gem install dpl 10 | - dpl --provider=heroku --app=backpulse --api-key=$HEROKU_API_KEY 11 | environment: 12 | name: Production 13 | url: https://backpulse.herokuapp.com/ 14 | only: 15 | - master 16 | when: always -------------------------------------------------------------------------------- /routes/admin/contact.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleContact(r *mux.Router) { 9 | r.Handle("/contact/{name}", ProtectedRoute(adminHandlers.GetContact)).Methods("GET") 10 | r.Handle("/contact/{name}", ProtectedRoute(adminHandlers.UpdateContact)).Methods("PUT") 11 | } 12 | -------------------------------------------------------------------------------- /constants/site_modules.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | type Module string 4 | 5 | var Modules []Module = []Module{ 6 | Galleries, 7 | Projects, 8 | Articles, 9 | Videos, 10 | Music, 11 | } 12 | 13 | const ( 14 | Galleries Module = "galleries" 15 | Projects Module = "projects" 16 | Articles Module = "articles" 17 | Videos Module = "videos" 18 | Music Module = "music" 19 | ) 20 | -------------------------------------------------------------------------------- /utils/sites.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/backpulse/core/models" 5 | ) 6 | 7 | // HasPremiumFeatures : i don't know 8 | func HasPremiumFeatures(site models.Site, size float64) bool { 9 | //TODO: Look into this function 10 | if len(site.Collaborators) > 1 { 11 | return true 12 | } 13 | if site.TotalSize > 500 { 14 | return true 15 | } 16 | return false 17 | } 18 | -------------------------------------------------------------------------------- /models/EmailVerification.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | //EmailVerification struct 10 | type EmailVerification struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | UserID bson.ObjectId `json:"user_id" bson:"user_id"` 13 | Email string `json:"email" bson:"email"` 14 | ExpireAt time.Time `json:"expire_at" bson:"expire_at"` 15 | } 16 | -------------------------------------------------------------------------------- /hack/docker/finalize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | set -e 4 | 5 | # Create user for backpulse 6 | addgroup -S backpulse 7 | adduser -G backpulse -H -D -g 'backpulse User' backpulse -h /data/backpulse -s /bin/bash && usermod -p '*' backpulse && passwd -u backpulse 8 | echo "export BACKPULSE_CUSTOM=${BACKPULSE_CUSTOM}" >> /etc/profile 9 | 10 | # Final cleaning 11 | rm /app/backpulse/docker/finalize.sh 12 | rm /app/backpulse/docker/nsswitch.conf 13 | -------------------------------------------------------------------------------- /models/AboutContent.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | ) 6 | 7 | //AboutContent struct 8 | type AboutContent struct { 9 | ID bson.ObjectId `json:"id" bson:"_id"` 10 | 11 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 12 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 13 | 14 | Name string `json:"name" bson:"name"` 15 | Descriptions []Translation `json:"descriptions" bson:"descriptions"` 16 | } 17 | -------------------------------------------------------------------------------- /routes/routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/backpulse/core/routes/admin" 5 | "github.com/backpulse/core/routes/client" 6 | "github.com/gorilla/mux" 7 | ) 8 | 9 | //NewRouter creates router with route handlers 10 | func NewRouter() *mux.Router { 11 | r := mux.NewRouter() 12 | 13 | adminRouter := r.PathPrefix("/admin").Subrouter() 14 | admin.HandleAdmin(adminRouter) 15 | 16 | clientRouter := r.PathPrefix("/{name}").Subrouter() 17 | client.HandleClient(clientRouter) 18 | 19 | return r 20 | } 21 | -------------------------------------------------------------------------------- /models/File.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // File struct 10 | type File struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 13 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 14 | 15 | Name string `json:"name" bson:"name"` 16 | URL string `json:"url" bson:"url"` 17 | Type string `json:"type" bson:"type"` 18 | Size float64 `json:"size" bson:"size"` 19 | 20 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 21 | } 22 | -------------------------------------------------------------------------------- /routes/admin/projects.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleProjects(r *mux.Router) { 9 | r.Handle("/project/{id}", ProtectedRoute(adminHandlers.GetProject)).Methods("GET") 10 | r.Handle("/projects/{name}", ProtectedRoute(adminHandlers.GetProjects)).Methods("GET") 11 | 12 | r.Handle("/projects/{name}", ProtectedRoute(adminHandlers.UpdateProject)).Methods("PUT") 13 | r.Handle("/project/{id}", ProtectedRoute(adminHandlers.DeleteProject)).Methods("DELETE") 14 | } 15 | -------------------------------------------------------------------------------- /routes/admin/files.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleFiles(r *mux.Router) { 9 | r.Handle("/files/{name}", ProtectedRoute(adminHandlers.GetFiles)).Methods("GET") 10 | r.Handle("/files/{name}", ProtectedRoute(adminHandlers.UploadFile)).Methods("POST") 11 | r.Handle("/files/{name}/{id}/{filename}", ProtectedRoute(adminHandlers.UpdateFilename)).Methods("PUT") 12 | r.Handle("/files/{name}/{ids}", ProtectedRoute(adminHandlers.DeleteFiles)).Methods("DELETE") 13 | } 14 | -------------------------------------------------------------------------------- /routes/admin/articles.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleArticles(r *mux.Router) { 9 | r.Handle("/articles/{name}", ProtectedRoute(adminHandlers.GetArticles)).Methods("GET") 10 | r.Handle("/articles/{name}/{id}", ProtectedRoute(adminHandlers.GetArticle)).Methods("GET") 11 | r.Handle("/articles/{name}", ProtectedRoute(adminHandlers.UpdateArticle)).Methods("PUT") 12 | 13 | r.Handle("/articles/{name}/{id}", ProtectedRoute(adminHandlers.DeleteArticle)).Methods("DELETE") 14 | } 15 | -------------------------------------------------------------------------------- /hack/docker/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | create_volume_subfolder() { 4 | # Create VOLUME subfolder 5 | for f in /data/log; do 6 | if ! test -d $f; then 7 | mkdir -p $f 8 | fi 9 | done 10 | } 11 | 12 | setids() { 13 | PUID=${PUID:-1000} 14 | PGID=${PGID:-1000} 15 | groupmod -o -g "$PGID" backpulse 16 | usermod -o -u "$PUID" backpulse 17 | } 18 | 19 | setids 20 | create_volume_subfolder 21 | 22 | # Exec CMD or S6 by default if nothing present 23 | if [ $# -gt 0 ];then 24 | exec "$@" 25 | else 26 | exec /bin/s6-svscan /app/backpulse/docker/s6/ 27 | fi 28 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # MongoDB server address (_mongodb://..._) 2 | MONGODB_URI=mongodb://mongodb:27017 3 | 4 | # MongoDB database name 5 | DATABASE=backpulse 6 | 7 | # A secret key to encrypt JWT 8 | # SECRET=? 9 | 10 | # A gmail address if you wish to send confirmation emails 11 | # GMAIL_ADDRESS=? 12 | 13 | # The password associated with the gmail address obviously 14 | # GMAIL_PASSWORD=? 15 | 16 | # Your Stripe Key if you wish to integrate Stripe 17 | # STRIPE_KEY=? 18 | 19 | # Your Google Cloud Storage Bucket's name to store user files (images, binaries, plain text...) 20 | # BUCKET_NAME=? 21 | 22 | # Your Google Cloud Storage Bucket's public url 23 | # BUCKET_PUB_URL=? -------------------------------------------------------------------------------- /models/Article.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // Article struct 10 | type Article struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | ShortID string `json:"short_id" bson:"short_id"` 13 | 14 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 15 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 16 | 17 | Title string `json:"title" bson:"title"` 18 | Content string `json:"content" bson:"content"` 19 | 20 | Index int `json:"index" bson:"index"` 21 | 22 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 23 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 24 | } 25 | -------------------------------------------------------------------------------- /routes/admin/tracks.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleTracks(r *mux.Router) { 9 | r.Handle("/tracks/{name}/indexes", ProtectedRoute(adminHandlers.UpdateTracksIndexes)).Methods("PUT") 10 | r.Handle("/tracks/{name}/{albumid}", ProtectedRoute(adminHandlers.AddTrack)).Methods("POST") 11 | 12 | r.Handle("/tracks/{name}/{id}", ProtectedRoute(adminHandlers.GetTrack)).Methods("GET") 13 | r.Handle("/tracks/{name}/{id}", ProtectedRoute(adminHandlers.DeleteTrack)).Methods("DELETE") 14 | 15 | r.Handle("/tracks/{name}/{id}", ProtectedRoute(adminHandlers.UpdateTrack)).Methods("PUT") 16 | } 17 | -------------------------------------------------------------------------------- /routes/admin/videos.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleVideos(r *mux.Router) { 9 | r.Handle("/videos/{name}/indexes", ProtectedRoute(adminHandlers.UpdateVideosIndexes)).Methods("PUT") 10 | r.Handle("/videos/{name}/{groupid}", ProtectedRoute(adminHandlers.AddVideo)).Methods("POST") 11 | 12 | r.Handle("/videos/{name}/{id}", ProtectedRoute(adminHandlers.GetVideo)).Methods("GET") 13 | r.Handle("/videos/{name}/{id}", ProtectedRoute(adminHandlers.DeleteVideo)).Methods("DELETE") 14 | 15 | r.Handle("/videos/{name}/{id}", ProtectedRoute(adminHandlers.UpdateVideo)).Methods("PUT") 16 | } 17 | -------------------------------------------------------------------------------- /utils/strings.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | "os" 9 | ) 10 | 11 | //GetStrings get strings 12 | func GetStrings(r *http.Request) map[string]string { 13 | lang := GetLang(r) 14 | jsonFile, err := os.Open("./strings.json") 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | defer jsonFile.Close() 19 | 20 | var data map[string]map[string]string 21 | 22 | byteValue, _ := ioutil.ReadAll(jsonFile) 23 | json.Unmarshal(byteValue, &data) 24 | 25 | if lang != "fr" { 26 | lang = "en" 27 | } 28 | 29 | return data[lang] 30 | } 31 | 32 | //GetLang returns user language 33 | func GetLang(r *http.Request) string { 34 | return r.Header.Get("Language") 35 | } 36 | -------------------------------------------------------------------------------- /models/VideoGroup.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // VideoGroup struct 10 | type VideoGroup struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | ShortID string `json:"short_id" bson:"short_id"` 13 | 14 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 15 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 16 | 17 | Title string `json:"title" bson:"title"` 18 | Image string `json:"image" bson:"image"` 19 | Videos []Video `json:"videos" bson:"videos"` 20 | 21 | Index int `json:"index" bson:"index"` 22 | 23 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 24 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 25 | } 26 | -------------------------------------------------------------------------------- /models/Project.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // Project struct 10 | type Project struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | ShortID string `json:"short_id" bson:"short_id"` 13 | 14 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 15 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 16 | 17 | Title string `json:"title" bson:"title"` 18 | Titles []Translation `json:"titles" bson:"titles"` 19 | Descriptions []Translation `json:"descriptions" bson:"descriptions"` 20 | 21 | URL string `json:"url" bson:"url"` 22 | 23 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 24 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 25 | } 26 | -------------------------------------------------------------------------------- /handlers/client/about.go: -------------------------------------------------------------------------------- 1 | package clienthandlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/mux" 7 | 8 | "github.com/backpulse/core/database" 9 | 10 | "github.com/backpulse/core/utils" 11 | ) 12 | 13 | // GetAbout : return about page 14 | func GetAbout(w http.ResponseWriter, r *http.Request) { 15 | vars := mux.Vars(r) 16 | name := vars["name"] 17 | 18 | site, err := database.GetSiteByName(name) 19 | if err != nil { 20 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 21 | return 22 | } 23 | 24 | about, err := database.GetAbout(site.ID) 25 | if err != nil { 26 | utils.RespondWithJSON(w, http.StatusNotFound, err.Error(), nil) 27 | return 28 | } 29 | utils.RespondWithJSON(w, http.StatusOK, "success", about) 30 | return 31 | 32 | } 33 | -------------------------------------------------------------------------------- /routes/admin/albums.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleAlbums(r *mux.Router) { 9 | 10 | r.Handle("/albums/{name}/indexes", ProtectedRoute(adminHandlers.UpdateAlbumsIndexes)).Methods("PUT") 11 | r.Handle("/albums/{name}", ProtectedRoute(adminHandlers.CreateAlbum)).Methods("POST") 12 | 13 | r.Handle("/albums/{name}", ProtectedRoute(adminHandlers.GetAlbums)).Methods("GET") 14 | r.Handle("/albums/{name}/{id}", ProtectedRoute(adminHandlers.GetAlbum)).Methods("GET") 15 | r.Handle("/albums/{name}/{id}", ProtectedRoute(adminHandlers.UpdateAlbum)).Methods("PUT") 16 | 17 | r.Handle("/albums/{name}/{id}", ProtectedRoute(adminHandlers.DeleteAlbum)).Methods("DELETE") 18 | } 19 | -------------------------------------------------------------------------------- /models/Album.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // Album struct 10 | type Album struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | ShortID string `json:"short_id" bson:"short_id"` 13 | 14 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 15 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 16 | 17 | Cover string `json:"cover" bson:"cover"` 18 | Title string `json:"title" bson:"title"` 19 | Description string `json:"description" bson:"description"` 20 | Tracks []Track `json:"tracks" bson:"tracks"` 21 | 22 | Index int `json:"index" bson:"index"` 23 | 24 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 25 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 26 | } 27 | -------------------------------------------------------------------------------- /models/ContactContent.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | ) 6 | 7 | //ContactContent struct 8 | type ContactContent struct { 9 | ID bson.ObjectId `json:"id" bson:"_id"` 10 | 11 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 12 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 13 | 14 | Name string `json:"name" bson:"name"` 15 | Phone string `json:"phone" bson:"phone"` 16 | Email string `json:"email" bson:"email"` 17 | Address string `json:"address" bson:"address"` 18 | 19 | FacebookURL string `json:"facebook_url" bson:"facebook_url"` 20 | InstagramURL string `json:"instagram_url" bson:"instagram_url"` 21 | TwitterURL string `json:"twitter_url" bson:"twitter_url"` 22 | 23 | CustomFields []string `json:"custom_fields" bson:"custom_fields"` 24 | } 25 | -------------------------------------------------------------------------------- /database/about.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/backpulse/core/models" 5 | "gopkg.in/mgo.v2/bson" 6 | ) 7 | 8 | // GetAbout : return about content of site 9 | func GetAbout(id bson.ObjectId) (models.AboutContent, error) { 10 | var about models.AboutContent 11 | err := DB.C(aboutCollection).Find(bson.M{ 12 | "site_id": id, 13 | }).One(&about) 14 | return about, err 15 | } 16 | 17 | // UpdateAbout : update about content of site 18 | func UpdateAbout(id bson.ObjectId, about models.AboutContent) error { 19 | _, err := DB.C(aboutCollection).Upsert(bson.M{ 20 | "site_id": id, 21 | }, bson.M{ 22 | "$set": bson.M{ 23 | "site_id": id, 24 | "owner_id": about.OwnerID, 25 | "name": about.Name, 26 | "descriptions": about.Descriptions, 27 | }, 28 | }) 29 | return err 30 | } 31 | -------------------------------------------------------------------------------- /handlers/client/contact.go: -------------------------------------------------------------------------------- 1 | package clienthandlers 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | "github.com/gorilla/mux" 8 | 9 | "github.com/backpulse/core/database" 10 | 11 | "github.com/backpulse/core/utils" 12 | ) 13 | 14 | // GetContact : return contact page 15 | func GetContact(w http.ResponseWriter, r *http.Request) { 16 | vars := mux.Vars(r) 17 | name := vars["name"] 18 | 19 | site, err := database.GetSiteByName(name) 20 | if err != nil { 21 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 22 | return 23 | } 24 | 25 | contact, err := database.GetContact(site.ID) 26 | if err != nil { 27 | log.Println(err) 28 | utils.RespondWithJSON(w, http.StatusNotFound, err.Error(), nil) 29 | return 30 | } 31 | utils.RespondWithJSON(w, http.StatusOK, "success", contact) 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /models/Track.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // Track struct 10 | type Track struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | ShortID string `json:"short_id" bson:"short_id"` 13 | 14 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 15 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 16 | 17 | AlbumID bson.ObjectId `json:"album_id" bson:"album_id"` 18 | 19 | Image string `json:"image" bson:"image"` 20 | Title string `json:"title" bson:"title"` 21 | URL string `json:"url" bson:"url"` 22 | Content string `json:"content" bson:"content"` 23 | 24 | Index int `json:"index" bson:"index"` 25 | 26 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 27 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 28 | } 29 | -------------------------------------------------------------------------------- /models/Video.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // Video struct 10 | type Video struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | ShortID string `json:"short_id" bson:"short_id"` 13 | 14 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 15 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 16 | 17 | Title string `json:"title" bson:"title"` 18 | Content string `json:"content" bson:"content"` 19 | YouTubeURL string `json:"youtube_url" bson:"youtube_url"` 20 | 21 | VideoGroupID bson.ObjectId `json:"video_group_id" bson:"video_group_id"` 22 | Index int `json:"index" bson:"index"` 23 | 24 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 25 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 26 | } 27 | -------------------------------------------------------------------------------- /routes/admin/videogroups.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleVideoGroups(r *mux.Router) { 9 | 10 | r.Handle("/videogroups/{name}/indexes", ProtectedRoute(adminHandlers.UpdateVideoGroupsIndexes)).Methods("PUT") 11 | r.Handle("/videogroups/{name}", ProtectedRoute(adminHandlers.CreateVideoGroup)).Methods("POST") 12 | 13 | r.Handle("/videogroups/{name}", ProtectedRoute(adminHandlers.GetVideoGroups)).Methods("GET") 14 | r.Handle("/videogroups/{name}/{id}", ProtectedRoute(adminHandlers.GetVideoGroup)).Methods("GET") 15 | r.Handle("/videogroups/{name}/{id}", ProtectedRoute(adminHandlers.UpdateVideoGroup)).Methods("PUT") 16 | 17 | r.Handle("/videogroups/{name}/{id}", ProtectedRoute(adminHandlers.DeleteVideoGroup)).Methods("DELETE") 18 | } 19 | -------------------------------------------------------------------------------- /routes/admin/photos.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handlePhotos(r *mux.Router) { 9 | r.Handle("/photos/{name}/create", ProtectedRoute(adminHandlers.CreatePhoto)).Methods("POST") 10 | r.Handle("/photos/{name}", ProtectedRoute(adminHandlers.GetPhotos)).Methods("GET") 11 | r.Handle("/photos/{name}/{id}", ProtectedRoute(adminHandlers.GetPhoto)).Methods("GET") 12 | r.Handle("/photos/{name}/{id}", ProtectedRoute(adminHandlers.UpdatePhoto)).Methods("PUT") 13 | r.Handle("/photos/{name}", ProtectedRoute(adminHandlers.UploadPhoto)).Methods("POST") 14 | r.Handle("/photos/{name}/{id}", ProtectedRoute(adminHandlers.UpdatePhotoFile)).Methods("POST") 15 | r.Handle("/photos/{ids}", ProtectedRoute(adminHandlers.DeletePhotos)).Methods("DELETE") 16 | 17 | r.Handle("/photos/{name}/{id}/indexes", ProtectedRoute(adminHandlers.UpdatePhotosIndexes)).Methods("PUT") 18 | } 19 | -------------------------------------------------------------------------------- /routes/admin/users.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleUsers(r *mux.Router) { 9 | r.HandleFunc("/users", adminHandlers.CreateUser).Methods("POST") 10 | r.HandleFunc("/users/authenticate", adminHandlers.AuthenticateUser).Methods("POST") 11 | r.HandleFunc("/users/verify/{id}", adminHandlers.VerifyUser).Methods("POST") 12 | 13 | r.Handle("/user", ProtectedRoute(adminHandlers.DeleteUser)).Methods("DELETE") 14 | r.Handle("/user/password", ProtectedRoute(adminHandlers.UpdateUserPassword)).Methods("PUT") 15 | r.Handle("/profile", ProtectedRoute(adminHandlers.UpdateUser)).Methods("PUT") 16 | 17 | r.Handle("/account/charge", ProtectedRoute(adminHandlers.ChargeAccount)).Methods("POST") 18 | r.Handle("/account/subscription", ProtectedRoute(adminHandlers.RemoveSubscription)).Methods("DELETE") 19 | r.Handle("/user", ProtectedRoute(adminHandlers.GetSelfUser)).Methods("GET") 20 | } 21 | -------------------------------------------------------------------------------- /routes/admin/galleries.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleGalleries(r *mux.Router) { 9 | r.Handle("/gallery/{id}", ProtectedRoute(adminHandlers.GetGallery)).Methods("GET") 10 | r.Handle("/gallery/{id}", ProtectedRoute(adminHandlers.DeleteGallery)).Methods("DELETE") 11 | r.Handle("/gallery/{id}", ProtectedRoute(adminHandlers.UpdateGallery)).Methods("PUT") 12 | 13 | r.Handle("/galleries/{name}/{galleryID}/preview/{id}", ProtectedRoute(adminHandlers.SetGalleryPreview)).Methods("PUT") 14 | r.Handle("/galleries/{name}/default/{id}", ProtectedRoute(adminHandlers.SetDefaultGallery)).Methods("PUT") 15 | r.Handle("/galleries/{name}/indexes", ProtectedRoute(adminHandlers.UpdateGalleriesIndexes)).Methods("PUT") 16 | r.Handle("/galleries/{name}/{galleryName}", ProtectedRoute(adminHandlers.CreateGallery)).Methods("POST") 17 | r.Handle("/galleries/{name}", ProtectedRoute(adminHandlers.GetGalleries)).Methods("GET") 18 | } 19 | -------------------------------------------------------------------------------- /models/Site.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/backpulse/core/constants" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | // Site struct 11 | type Site struct { 12 | ID bson.ObjectId `json:"id" bson:"_id"` 13 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 14 | 15 | DisplayName string `json:"display_name" bson:"display_name"` 16 | Name string `json:"name" bson:"name"` 17 | 18 | Modules []constants.Module `json:"modules" bson:"modules"` 19 | 20 | /* Emails */ 21 | Collaborators []Collaborator `json:"collaborators" bson:"collaborators"` 22 | 23 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 24 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 25 | 26 | /* Dynamic data */ 27 | Favorite bool `json:"favorite" bson:"-"` 28 | Role string `json:"role" bson:"-"` 29 | TotalSize float64 `json:"total_size" bson:"-"` 30 | } 31 | 32 | type Collaborator struct { 33 | Email string `json:"email" bson:"email"` 34 | Role string `json:"role" bson:"role"` 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/backpulse/core 2 | 3 | go 1.11 4 | 5 | require ( 6 | cloud.google.com/go v0.37.2 7 | github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f 8 | github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 9 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 10 | github.com/gorilla/context v1.1.1 11 | github.com/gorilla/mux v1.6.2 12 | github.com/joho/godotenv v1.3.0 13 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 14 | github.com/rs/cors v1.6.0 15 | github.com/sirupsen/logrus v1.3.0 16 | github.com/stripe/stripe-go v59.1.0+incompatible 17 | github.com/teris-io/shortid v0.0.0-20160104014424-6c56cef5189c 18 | github.com/urfave/negroni v1.0.0 19 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 20 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a 21 | google.golang.org/api v0.3.0 22 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc 23 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df 24 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce 25 | ) 26 | -------------------------------------------------------------------------------- /database/contact.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/backpulse/core/models" 5 | "gopkg.in/mgo.v2/bson" 6 | ) 7 | 8 | // UpdateContact : update contact content 9 | func UpdateContact(id bson.ObjectId, content models.ContactContent) error { 10 | _, err := DB.C(contactCollection).Upsert(bson.M{ 11 | "site_id": id, 12 | }, bson.M{ 13 | "$set": bson.M{ 14 | "site_id": id, 15 | "owner_id": content.OwnerID, 16 | "name": content.Name, 17 | "phone": content.Phone, 18 | "email": content.Email, 19 | "address": content.Address, 20 | "facebook_url": content.FacebookURL, 21 | "instagram_url": content.InstagramURL, 22 | "twitter_url": content.TwitterURL, 23 | }, 24 | }) 25 | return err 26 | } 27 | 28 | // GetContact : return contact content 29 | func GetContact(id bson.ObjectId) (models.ContactContent, error) { 30 | var contactContent models.ContactContent 31 | err := DB.C(contactCollection).Find(bson.M{ 32 | "site_id": id, 33 | }).One(&contactContent) 34 | return contactContent, err 35 | } 36 | -------------------------------------------------------------------------------- /models/Gallery.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // Gallery struct 10 | type Gallery struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | ShortID string `json:"short_id" bson:"short_id"` 13 | 14 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 15 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 16 | 17 | DefaultGallery bool `json:"default_gallery" bson:"default_gallery"` 18 | 19 | PreviewPhoto Photo `json:"preview_photo" bson:"-"` 20 | PreviewPhotoID bson.ObjectId `json:"preview_photo_id" bson:"preview_photo_id,omitempty"` 21 | 22 | Title string `json:"title" bson:"title"` 23 | Titles []Translation `json:"titles" bson:"titles"` 24 | Descriptions []Translation `json:"descriptions" bson:"descriptions"` 25 | 26 | Photos []Photo `json:"photos" bson:"photos,omitempty"` 27 | 28 | Index int `json:"index" bson:"index"` 29 | 30 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 31 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 32 | } 33 | -------------------------------------------------------------------------------- /models/User.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | //User struct 10 | type User struct { 11 | ID bson.ObjectId `json:"id" bson:"_id,omitempty"` 12 | 13 | FullName string `json:"fullname" bson:"fullname"` 14 | Email string `json:"email" bson:"email"` 15 | Password string `json:"password" bson:"password"` 16 | 17 | Country string `json:"country" bson:"country"` 18 | City string `json:"city" bson:"city"` 19 | Address string `json:"address" bson:"address"` 20 | ZIP string `json:"zip" bson:"zip"` 21 | State string `json:"state" bson:"state"` 22 | 23 | EmailVerified bool `json:"email_verified" bson:"email_verified"` 24 | 25 | StripeID string `json:"-" bson:"stripe_id"` 26 | ActiveSubscriptionID string `json:"-" bson:"active_subscription_id"` 27 | Professional bool `json:"professional" bson:"professional"` 28 | 29 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 30 | UpdatedAt time.Time `json:"updated_at" bson:"updated_at"` 31 | 32 | FavoriteSites []bson.ObjectId `json:"favorite_sites" bson:"favorite_sites"` 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Backpulse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /database/files.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/backpulse/core/models" 5 | "gopkg.in/mgo.v2/bson" 6 | ) 7 | 8 | // InsertFile : create file in db 9 | func InsertFile(file models.File) error { 10 | err := DB.C(filesCollection).Insert(file) 11 | return err 12 | } 13 | 14 | // GetSiteFiles : return array of file for site 15 | func GetSiteFiles(id bson.ObjectId) ([]models.File, error) { 16 | var files []models.File 17 | err := DB.C(filesCollection).Find(bson.M{ 18 | "site_id": id, 19 | }).All(&files) 20 | return files, err 21 | } 22 | 23 | // DeleteFile : remove file from db 24 | func DeleteFile(fileID bson.ObjectId, siteID bson.ObjectId) error { 25 | err := DB.C(filesCollection).Remove(bson.M{ 26 | "_id": fileID, 27 | "site_id": siteID, 28 | }) 29 | return err 30 | } 31 | 32 | // UpdateFilename : change the name of a file 33 | func UpdateFilename(fileID bson.ObjectId, filename string, siteID bson.ObjectId) error { 34 | err := DB.C(filesCollection).Update(bson.M{ 35 | "_id": fileID, 36 | "site_id": siteID, 37 | }, bson.M{ 38 | "$set": bson.M{ 39 | "name": filename, 40 | }, 41 | }) 42 | return err 43 | } 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS binaryBuilder 2 | # Install build deps 3 | RUN apk --no-cache --no-progress add --virtual build-deps build-base git 4 | 5 | # Build project 6 | WORKDIR /go/src/github.com/backpulse/core 7 | COPY . . 8 | RUN make build 9 | 10 | FROM alpine:latest 11 | # Install system utils & runtime dependencies 12 | ADD https://github.com/tianon/gosu/releases/download/1.11/gosu-amd64 /usr/sbin/gosu 13 | RUN chmod +x /usr/sbin/gosu \ 14 | && echo http://dl-2.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories \ 15 | && apk --no-cache --no-progress add bash s6 shadow 16 | 17 | # Configure LibC Name Service 18 | COPY hack/docker/nsswitch.conf /etc/nsswitch.conf 19 | 20 | # Copy target app from binaryBuilder stage 21 | WORKDIR /app/backpulse 22 | COPY hack/docker docker 23 | COPY .env ./ 24 | COPY --from=binaryBuilder /go/src/github.com/backpulse/core/backpulse . 25 | 26 | # Finalize s6 configure 27 | RUN ./docker/finalize.sh 28 | 29 | # Configure Docker Container 30 | VOLUME ["/data"] 31 | 32 | # backend data interface agent. 33 | EXPOSE 8000 34 | 35 | ENTRYPOINT ["/app/backpulse/docker/start.sh"] 36 | CMD ["/bin/s6-svscan", "/app/backpulse/docker/s6/"] 37 | -------------------------------------------------------------------------------- /models/Photo.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | ) 8 | 9 | // Photo struct 10 | type Photo struct { 11 | ID bson.ObjectId `json:"id" bson:"_id"` 12 | OwnerID bson.ObjectId `json:"owner_id" bson:"owner_id"` 13 | SiteID bson.ObjectId `json:"site_id" bson:"site_id"` 14 | 15 | Title string `json:"title" bson:"title"` 16 | Content string `json:"content" bson:"content"` 17 | 18 | URL string `json:"url" bson:"url"` 19 | Width int `json:"width" bson:"width"` 20 | Height int `json:"height" bson:"height"` 21 | Format string `json:"format" bson:"format"` 22 | 23 | GalleryName string `json:"gallery_name,omitempty" bson:"gallery_name,omitempty"` 24 | 25 | IsGallery bool `json:"is_gallery" bson:"is_gallery"` 26 | GalleryID *bson.ObjectId `json:"gallery_id,omitempty" bson:"gallery_id,omitempty"` 27 | 28 | IsProject bool `json:"is_project" bson:"is_project"` 29 | ProjectID bson.ObjectId `json:"project_id" bson:"project_id,omitempty"` 30 | Size float64 `json:"size" bson:"size"` 31 | 32 | Index int `json:"index" bson:"index"` 33 | 34 | CreatedAt time.Time `json:"created_at" bson:"created_at"` 35 | } 36 | -------------------------------------------------------------------------------- /utils/config.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | 7 | "github.com/backpulse/core/models" 8 | ) 9 | 10 | func loadConfiguration(file string) (models.Config, error) { 11 | var config models.Config 12 | configFile, err := os.Open(file) 13 | defer configFile.Close() 14 | if err != nil { 15 | return models.Config{}, err 16 | } 17 | jsonParser := json.NewDecoder(configFile) 18 | jsonParser.Decode(&config) 19 | return config, nil 20 | } 21 | 22 | //GetConfig returns config object 23 | func GetConfig() models.Config { 24 | // Use by default ./config.json 25 | config, err := loadConfiguration("./config.json") 26 | if err != nil { 27 | // If it doesn't exist, take environnement variables 28 | config = models.Config{ 29 | URI: os.Getenv("MONGODB_URI"), 30 | Database: os.Getenv("DATABASE"), 31 | Secret: os.Getenv("SECRET"), 32 | GmailAddress: os.Getenv("GMAIL_ADDRESS"), 33 | GmailPassword: os.Getenv("GMAIL_PASSWORD"), 34 | StripeKey: os.Getenv("STRIPE_KEY"), 35 | BucketName: os.Getenv("BUCKET_NAME"), 36 | BucketPubURL: os.Getenv("BUCKET_PUB_URL"), 37 | } 38 | return config 39 | } 40 | return config 41 | } 42 | -------------------------------------------------------------------------------- /handlers/client/articles.go: -------------------------------------------------------------------------------- 1 | package clienthandlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/backpulse/core/database" 7 | "github.com/backpulse/core/utils" 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | // GetArticle : return specific article 12 | func GetArticle(w http.ResponseWriter, r *http.Request) { 13 | vars := mux.Vars(r) 14 | id := vars["short_id"] 15 | article, err := database.GetArticleByShortID(id) 16 | if err != nil { 17 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 18 | return 19 | } 20 | utils.RespondWithJSON(w, http.StatusOK, "success", article) 21 | return 22 | } 23 | 24 | // GetArticles : return array of article 25 | func GetArticles(w http.ResponseWriter, r *http.Request) { 26 | vars := mux.Vars(r) 27 | name := vars["name"] 28 | 29 | site, err := database.GetSiteByName(name) 30 | if err != nil { 31 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 32 | return 33 | } 34 | 35 | articles, err := database.GetArticles(site.ID) 36 | if err != nil { 37 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 38 | return 39 | } 40 | 41 | utils.RespondWithJSON(w, http.StatusOK, "success", articles) 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | mgo "gopkg.in/mgo.v2" 6 | ) 7 | 8 | // DB : Database holder 9 | var DB *mgo.Database 10 | 11 | const ( 12 | usersCollection string = "users" 13 | emailVerificationsCollection string = "email_verifications" 14 | sitesCollection string = "sites" 15 | contactCollection string = "contact" 16 | aboutCollection string = "about" 17 | articlesCollection string = "articles" 18 | projectsCollection string = "projects" 19 | galleriesCollection string = "galleries" 20 | photosCollection string = "photos" 21 | videoGroupsCollection string = "videogroups" 22 | videosCollection string = "videos" 23 | filesCollection string = "files" 24 | albumsCollection string = "albums" 25 | tracksCollection string = "tracks" 26 | ) 27 | 28 | //Connect : Connect to MongoDB 29 | func Connect(server string, database string) { 30 | session, err := mgo.Dial(server) 31 | 32 | if err != nil { 33 | logrus.Fatal("Database: ERROR", err) 34 | } 35 | DB = session.DB(database) 36 | logrus.Infoln("Database: OK") 37 | } 38 | -------------------------------------------------------------------------------- /utils/users.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/backpulse/core/models" 8 | jwt "github.com/dgrijalva/jwt-go" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | //NewJWT : generates new jwt 13 | func NewJWT(user models.User, expire int) (string, error) { 14 | config := GetConfig() 15 | 16 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 17 | "email": user.Email, 18 | "id": user.ID.Hex(), 19 | "exp": time.Now().Add(time.Hour * time.Duration(expire)), /* Token expires in x hours */ 20 | }) 21 | /* Sign token and get string */ 22 | tokenString, err := token.SignedString([]byte(config.Secret)) 23 | if err != nil { 24 | return "", err 25 | } 26 | return tokenString, nil 27 | } 28 | 29 | //GetUserObjectID : return id of jwt as type object id 30 | func GetUserObjectID(r *http.Request) bson.ObjectId { 31 | decoded := GetDecodedJWT(r) 32 | id := decoded["id"].(string) 33 | return bson.ObjectIdHex(id) 34 | } 35 | 36 | // IsAuthorized : Check if a user is authorized to edit site 37 | func IsAuthorized(site models.Site, user models.User) bool { 38 | if site.OwnerID == user.ID { 39 | return true 40 | } 41 | for _, collaborator := range site.Collaborators { 42 | if collaborator.Email == user.Email { 43 | return true 44 | } 45 | } 46 | return false 47 | } 48 | -------------------------------------------------------------------------------- /handlers/client/projects.go: -------------------------------------------------------------------------------- 1 | package clienthandlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/backpulse/core/database" 7 | "github.com/backpulse/core/utils" 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | // GetProjects return array of projects 12 | func GetProjects(w http.ResponseWriter, r *http.Request) { 13 | vars := mux.Vars(r) 14 | name := vars["name"] 15 | 16 | site, err := database.GetSiteByName(name) 17 | if err != nil { 18 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 19 | return 20 | } 21 | 22 | projects, err := database.GetProjects(site.ID) 23 | if err != nil { 24 | utils.RespondWithJSON(w, http.StatusNotFound, err.Error(), nil) 25 | return 26 | } 27 | utils.RespondWithJSON(w, http.StatusOK, "success", projects) 28 | return 29 | } 30 | 31 | // GetProject return specific project 32 | func GetProject(w http.ResponseWriter, r *http.Request) { 33 | vars := mux.Vars(r) 34 | name := vars["name"] 35 | id := vars["short_id"] 36 | 37 | _, err := database.GetSiteByName(name) 38 | if err != nil { 39 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 40 | return 41 | } 42 | 43 | project, err := database.GetProjectByShortID(id) 44 | if err != nil { 45 | utils.RespondWithJSON(w, http.StatusNotFound, err.Error(), nil) 46 | return 47 | } 48 | utils.RespondWithJSON(w, http.StatusOK, "success", project) 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/backpulse/core/database" 10 | "github.com/backpulse/core/routes" 11 | "github.com/backpulse/core/utils" 12 | "github.com/joho/godotenv" 13 | "github.com/rs/cors" 14 | ) 15 | 16 | var ( 17 | envFile string 18 | ) 19 | 20 | func init() { 21 | // env file and will load them into ENV for this process. 22 | // will not overload value that defined in ENV had present. 23 | flag.StringVar(&envFile, "env", ".env", "env config file") 24 | } 25 | 26 | func main() { 27 | flag.Parse() 28 | 29 | log.SetFlags(log.LstdFlags | log.Lshortfile) 30 | godotenv.Load(envFile) 31 | config := utils.GetConfig() 32 | 33 | database.Connect(config.URI, config.Database) 34 | utils.InitGoogleCloud() 35 | utils.InitStripe() 36 | 37 | r := routes.NewRouter() 38 | c := cors.New(cors.Options{ 39 | AllowedOrigins: []string{"*"}, 40 | AllowedHeaders: []string{"Access-Control-Allow-Origin", "origin", "X-Requested-With", "Authorization", "Content-Type", "Language"}, 41 | AllowedMethods: []string{"DELETE", "POST", "GET", "PUT"}, 42 | }) 43 | 44 | handler := c.Handler(r) 45 | 46 | var port string 47 | if os.Getenv("PORT") == "" { 48 | port = ":8000" 49 | } else { 50 | port = ":" + os.Getenv("PORT") 51 | } 52 | 53 | err := http.ListenAndServe(port, handler) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /routes/admin/admin.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "net/http" 5 | 6 | jwtmiddleware "github.com/auth0/go-jwt-middleware" 7 | "github.com/backpulse/core/database" 8 | "github.com/backpulse/core/utils" 9 | jwt "github.com/dgrijalva/jwt-go" 10 | "github.com/gorilla/mux" 11 | "github.com/urfave/negroni" 12 | ) 13 | 14 | var jwtMiddleware *jwtmiddleware.JWTMiddleware 15 | 16 | // HandleAdmin : setup all admin routes 17 | func HandleAdmin(r *mux.Router) *mux.Router { 18 | config := utils.GetConfig() 19 | 20 | jwtMiddleware = jwtmiddleware.New(jwtmiddleware.Options{ 21 | ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { 22 | return []byte(config.Secret), nil 23 | }, 24 | SigningMethod: jwt.SigningMethodHS256, 25 | }) 26 | 27 | handleUsers(r) 28 | handleSites(r) 29 | handleContact(r) 30 | handleConstants(r) 31 | handleProjects(r) 32 | handleAbout(r) 33 | handlePhotos(r) 34 | handleGalleries(r) 35 | handleArticles(r) 36 | handleVideos(r) 37 | handleVideoGroups(r) 38 | handleFiles(r) 39 | handleAlbums(r) 40 | handleTracks(r) 41 | 42 | return r 43 | } 44 | 45 | // ProtectedRoute : returns a JWT protected route handler 46 | func ProtectedRoute(next func(w http.ResponseWriter, r *http.Request)) *negroni.Negroni { 47 | return negroni.New(negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), negroni.WrapFunc(func(w http.ResponseWriter, r *http.Request) { 48 | id := utils.GetUserObjectID(r) 49 | _, err := database.GetUserByID(id) 50 | if err != nil { 51 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 52 | return 53 | } 54 | next(w, r) 55 | })) 56 | } 57 | -------------------------------------------------------------------------------- /handlers/client/albums.go: -------------------------------------------------------------------------------- 1 | package clienthandlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/backpulse/core/database" 7 | "github.com/backpulse/core/utils" 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | // GetAlbums : return array of album 12 | func GetAlbums(w http.ResponseWriter, r *http.Request) { 13 | vars := mux.Vars(r) 14 | name := vars["name"] 15 | 16 | site, err := database.GetSiteByName(name) 17 | if err != nil { 18 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 19 | return 20 | } 21 | 22 | albums, err := database.GetAlbums(site.ID) 23 | if err != nil { 24 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 25 | return 26 | } 27 | 28 | utils.RespondWithJSON(w, http.StatusOK, "success", albums) 29 | return 30 | } 31 | 32 | // GetAlbum : return specific album 33 | func GetAlbum(w http.ResponseWriter, r *http.Request) { 34 | vars := mux.Vars(r) 35 | id := vars["short_id"] 36 | 37 | album, err := database.GetAlbumByShortID(id) 38 | if err != nil { 39 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 40 | return 41 | } 42 | 43 | utils.RespondWithJSON(w, http.StatusOK, "success", album) 44 | return 45 | } 46 | 47 | // GetTrack : return specific track informations 48 | func GetTrack(w http.ResponseWriter, r *http.Request) { 49 | vars := mux.Vars(r) 50 | id := vars["short_id"] 51 | 52 | track, err := database.GetTrackByShortID(id) 53 | if err != nil { 54 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 55 | return 56 | } 57 | 58 | utils.RespondWithJSON(w, http.StatusOK, "success", track) 59 | return 60 | } 61 | -------------------------------------------------------------------------------- /strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "fr": { 3 | "ERROR": "Erreur", 4 | "NOT_FOUND": "Non trouvé", 5 | "USERNAME_ALREADY_EXISTS": "Ce nom d'utilisateur est déjà utilisé.", 6 | "USERNAME_TOO_SHORT": "Ce nom d'utilisateur est trop court.", 7 | "USERNAME_TOO_LONG": "Ce nom d'utilisateur est trop long.", 8 | "PASSWORD_TOO_SHORT": "Ce mot de passe est trop court.", 9 | "PASSWORD_TOO_LONG": "Ce mot de passe est trop long.", 10 | "NAME_TOO_LONG": "Le nom est trop long.", 11 | "EMAIL_INVALID_FORMAT": "Ce format d'adresse email est invalide.", 12 | "EMAIL_ALREADY_EXISTS": "Cette adresse email a déjà été atribuée.", 13 | "WRONG_LAST_PASSWORD": "Le mot de passe est éronné", 14 | "WRONG_CREDENTIALS": "L'identifiant ou le mot de passe est éronné.", 15 | "UNAUTHORIZED": "Non autorisé" 16 | }, 17 | "en": { 18 | "ERROR": "Error", 19 | "NOT_FOUND": "Not found", 20 | "USERNAME_ALREADY_EXISTS": "This username is already taken.", 21 | "USERNAME_TOO_SHORT": "This is username is too short.", 22 | "USERNAME_TOO_LONG": "This username is too long.", 23 | "PASSWORD_TOO_SHORT": "This password is too short.", 24 | "PASSWORD_TOO_LONG": "This password is too long.", 25 | "NAME_TOO_LONG": "Name is too long.", 26 | "EMAIL_INVALID_FORMAT": "Invalid email format.", 27 | "EMAIL_ALREADY_EXISTS": "This email has already been assigned.", 28 | "WRONG_LAST_PASSWORD": "This is not the correct password", 29 | "WRONG_CREDENTIALS": "Username/Email or password is wrong.", 30 | "UNAUTHORIZED": "Unauthorized" 31 | } 32 | } -------------------------------------------------------------------------------- /routes/admin/sites.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | adminHandlers "github.com/backpulse/core/handlers/admin" 5 | "github.com/gorilla/mux" 6 | ) 7 | 8 | func handleSites(r *mux.Router) { 9 | /* Sites */ 10 | r.Handle("/sites", ProtectedRoute(adminHandlers.GetSites)).Methods("GET") 11 | r.Handle("/sites/{name}/overview", ProtectedRoute(adminHandlers.GetOverview)).Methods("GET") 12 | r.Handle("/sites/{name}", ProtectedRoute(adminHandlers.GetSite)).Methods("GET") 13 | r.Handle("/sites/{name}", ProtectedRoute(adminHandlers.UpdateSite)).Methods("PUT") 14 | r.Handle("/sites/{name}", ProtectedRoute(adminHandlers.DeleteSite)).Methods("DELETE") 15 | r.Handle("/sites", ProtectedRoute(adminHandlers.CreateSite)).Methods("POST") 16 | 17 | /* Favorite */ 18 | r.Handle("/sites/favorite/{name}", ProtectedRoute(adminHandlers.Favorite)).Methods("PUT") 19 | 20 | /* Modules */ 21 | r.Handle("/sites/{name}/modules/{module}", ProtectedRoute(adminHandlers.AddModule)).Methods("POST") 22 | r.Handle("/sites/{name}/modules/{module}", ProtectedRoute(adminHandlers.RemoveModule)).Methods("DELETE") 23 | r.Handle("/sites/{name}/modules", ProtectedRoute(adminHandlers.GetSiteModules)).Methods("GET") 24 | 25 | /* Collaborators */ 26 | r.Handle("/sites/{name}/collaborators", ProtectedRoute(adminHandlers.GetCollaborators)).Methods("GET") 27 | r.Handle("/sites/{name}/collaborators/{email}", ProtectedRoute(adminHandlers.AddCollaborator)).Methods("POST") 28 | r.Handle("/sites/{name}/collaborators/{email}", ProtectedRoute(adminHandlers.RemoveCollaborator)).Methods("DELETE") 29 | 30 | /* Transfer */ 31 | r.Handle("/sites/{name}/transfer/{email}", ProtectedRoute(adminHandlers.TransferSite)).Methods("POST") 32 | } 33 | -------------------------------------------------------------------------------- /handlers/client/videos.go: -------------------------------------------------------------------------------- 1 | package clienthandlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/backpulse/core/database" 7 | "github.com/backpulse/core/utils" 8 | "github.com/gorilla/mux" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | // GetVideoGroups : return array of video group 13 | func GetVideoGroups(w http.ResponseWriter, r *http.Request) { 14 | vars := mux.Vars(r) 15 | name := vars["name"] 16 | 17 | site, err := database.GetSiteByName(name) 18 | if err != nil { 19 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 20 | return 21 | } 22 | 23 | videogroups, err := database.GetVideoGroups(site.ID) 24 | if err != nil { 25 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 26 | return 27 | } 28 | 29 | utils.RespondWithJSON(w, http.StatusOK, "success", videogroups) 30 | return 31 | } 32 | 33 | // GetVideoGroup // return specific video group 34 | func GetVideoGroup(w http.ResponseWriter, r *http.Request) { 35 | vars := mux.Vars(r) 36 | id := vars["short_id"] 37 | 38 | videogroup, err := database.GetVideoGroupByShortID(id) 39 | if err != nil { 40 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 41 | return 42 | } 43 | 44 | utils.RespondWithJSON(w, http.StatusOK, "success", videogroup) 45 | return 46 | } 47 | 48 | // GetVideo : return specific video informations 49 | func GetVideo(w http.ResponseWriter, r *http.Request) { 50 | vars := mux.Vars(r) 51 | id := vars["id"] 52 | 53 | video, err := database.GetVideo(bson.ObjectIdHex(id)) 54 | if err != nil { 55 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 56 | return 57 | } 58 | 59 | utils.RespondWithJSON(w, http.StatusOK, "success", video) 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /utils/response.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/backpulse/core/models" 8 | gomail "gopkg.in/gomail.v2" 9 | ) 10 | 11 | //Response Struct 12 | type Response struct { 13 | Status string `json:"status"` 14 | Code int `json:"code"` 15 | Message string `json:"message"` 16 | Payload interface{} `json:"payload"` 17 | } 18 | 19 | //RespondWithJSON : Respond with JSON 20 | func RespondWithJSON(w http.ResponseWriter, code int, message string, payload interface{}) { 21 | var status string 22 | if code >= 200 && code <= 299 { 23 | status = "success" 24 | } else { 25 | status = "error" 26 | } 27 | 28 | response, _ := json.MarshalIndent(Response{ 29 | Status: status, 30 | Code: code, 31 | Message: message, 32 | Payload: payload, 33 | }, "", " ") 34 | 35 | w.Header().Set("Content-Type", "application/json") 36 | w.WriteHeader(code) 37 | w.Write(response) 38 | } 39 | 40 | //SendVerificationMail : send verification email to user 41 | func SendVerificationMail(email string, verification models.EmailVerification) error { 42 | config := GetConfig() 43 | m := gomail.NewMessage() 44 | 45 | m.SetHeader("From", "no-reply@backpulse.io") 46 | m.SetHeader("To", email) 47 | 48 | m.SetHeader("Subject", "Please verify your email address") 49 | 50 | link := "https://www.backpulse.io/verify/" + verification.ID.Hex() 51 | linkAsATag := "" + link + "" 52 | 53 | m.SetBody("text/html", `Please click the following link to confirm that `+email+` is your email address.
`+linkAsATag+`

Thanks for using Backpulse!`) 54 | 55 | d := gomail.NewDialer("smtp.gmail.com", 465, config.GmailAddress, config.GmailPassword) 56 | 57 | err := d.DialAndSend(m) 58 | return err 59 | } 60 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN_TARGET = backpulse 2 | 3 | GO ?= go 4 | GO_ON ?= GO111MODULE=on go 5 | GO_OFF ?= GO111MODULE=off go 6 | GOFMT ?= gofmt "-s" 7 | PACKAGES ?= $(shell GO111MODULE=on $(GO) list ./...) 8 | VETPACKAGES ?= $(shell GO111MODULE=on $(GO) list ./...) 9 | GOFILES := $(shell find . -name "*.go" -type f) 10 | 11 | .PHONY: default 12 | default: build 13 | 14 | .PHONY: build 15 | build: 16 | $(GO_ON) mod download 17 | $(GO_ON) build -o $(BIN_TARGET) github.com/backpulse/core 18 | 19 | .PHONY: ci 20 | ci: misspell lint vet test 21 | 22 | .PHONY: test 23 | test: fmt 24 | $(GO_ON) test -race ./... 25 | 26 | .PHONY: fmt 27 | fmt: 28 | $(GOFMT) -w $(GOFILES) 29 | 30 | .PHONY: fmt-check 31 | fmt-check: 32 | @diff=$$($(GOFMT) -d $(GOFILES)); \ 33 | if [ -n "$$diff" ]; then \ 34 | echo "Please run 'make fmt' and commit the result:"; \ 35 | echo "$${diff}"; \ 36 | exit 1; \ 37 | fi; 38 | 39 | .PHONY: vet 40 | vet: 41 | $(GO_ON) vet $(VETPACKAGES) 42 | 43 | .PHONY: lint 44 | lint: 45 | @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ 46 | $(GO_OFF) get -u golang.org/x/lint/golint; \ 47 | fi 48 | for PKG in $(PACKAGES); do golint -min_confidence 1.0 -set_exit_status $$PKG || exit 1; done; 49 | 50 | .PHONY: misspell-check 51 | misspell-check: 52 | @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ 53 | $(GO_OFF) get -u github.com/client9/misspell/cmd/misspell; \ 54 | fi 55 | misspell -error $(GOFILES) 56 | 57 | .PHONY: misspell 58 | misspell: 59 | @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ 60 | $(GO_OFF) get -u github.com/client9/misspell/cmd/misspell; \ 61 | fi 62 | misspell -w $(GOFILES) 63 | 64 | .PHONY: tools 65 | tools: 66 | $(GO_OFF) get golang.org/x/lint/golint 67 | $(GO_OFF) get github.com/client9/misspell/cmd/misspell 68 | 69 | .PHONY: clean 70 | clean: 71 | $(GO_ON) clean -r ./... 72 | -rm -f $(BIN_TARGET) 73 | -------------------------------------------------------------------------------- /database/articles.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/backpulse/core/models" 8 | "github.com/teris-io/shortid" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | // GetArticles : return array of articles of site 13 | func GetArticles(id bson.ObjectId) ([]models.Article, error) { 14 | var articles []models.Article 15 | err := DB.C(articlesCollection).Find(bson.M{ 16 | "site_id": id, 17 | }).All(&articles) 18 | return articles, err 19 | } 20 | 21 | // RemoveArticle : remove article from db 22 | func RemoveArticle(id bson.ObjectId) error { 23 | err := DB.C(articlesCollection).RemoveId(id) 24 | return err 25 | } 26 | 27 | // GetArticle : return single article based by short_id 28 | func GetArticleByShortID(shortID string) (models.Article, error) { 29 | var article models.Article 30 | err := DB.C(articlesCollection).Find(bson.M{ 31 | "short_id": shortID, 32 | }).One(&article) 33 | return article, err 34 | } 35 | 36 | // GetArticle : return single article based by short_id 37 | func GetArticle(id bson.ObjectId) (models.Article, error) { 38 | var article models.Article 39 | err := DB.C(articlesCollection).FindId(id).One(&article) 40 | return article, err 41 | } 42 | 43 | // UpsertArticle : create/update article 44 | func UpsertArticle(article models.Article) (models.Article, error) { 45 | article.UpdatedAt = time.Now() 46 | if article.ID == "" { 47 | article.CreatedAt = time.Now() 48 | article.ID = bson.NewObjectId() 49 | article.ShortID, _ = shortid.Generate() 50 | err := DB.C(articlesCollection).Insert(article) 51 | log.Println(err) 52 | return article, err 53 | } 54 | err := DB.C(articlesCollection).UpdateId(article.ID, bson.M{ 55 | "$set": bson.M{ 56 | "title": article.Title, 57 | "content": article.Content, 58 | "updated_at": article.UpdatedAt, 59 | }, 60 | }) 61 | log.Println(err) 62 | 63 | return article, err 64 | } 65 | -------------------------------------------------------------------------------- /database/videos.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/backpulse/core/models" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | // AddVideo : add video to db 11 | func AddVideo(video models.Video) error { 12 | video.UpdatedAt = time.Now() 13 | video.CreatedAt = time.Now() 14 | err := DB.C(videosCollection).Insert(video) 15 | return err 16 | } 17 | 18 | // GetVideo : Return specific video by ObjectID 19 | func GetVideo(videoID bson.ObjectId) (models.Video, error) { 20 | var video models.Video 21 | err := DB.C(videosCollection).FindId(videoID).One(&video) 22 | return video, err 23 | } 24 | 25 | // GetGroupVideos : return array of video from a videogroup 26 | func GetGroupVideos(id bson.ObjectId) ([]models.Video, error) { 27 | var videos []models.Video 28 | err := DB.C(videosCollection).Find(bson.M{ 29 | "video_group_id": id, 30 | }).All(&videos) 31 | return videos, err 32 | } 33 | 34 | // Updatevideo : update video informations (title, content, youtube_url) 35 | func UpdateVideo(id bson.ObjectId, video models.Video) error { 36 | err := DB.C(videosCollection).UpdateId(id, bson.M{ 37 | "$set": bson.M{ 38 | "title": video.Title, 39 | "content": video.Content, 40 | "youtube_url": video.YouTubeURL, 41 | }, 42 | }) 43 | return err 44 | } 45 | 46 | // RemoveVideo : remove video from db 47 | func RemoveVideo(id bson.ObjectId) error { 48 | err := DB.C(videosCollection).RemoveId(id) 49 | return err 50 | } 51 | 52 | // UpdateVideosIndexes : update order of videos 53 | func UpdateVideosIndexes(siteID bson.ObjectId, videos []models.Video) error { 54 | for _, video := range videos { 55 | err := DB.C(videosCollection).Update(bson.M{ 56 | "site_id": siteID, 57 | "_id": video.ID, 58 | }, bson.M{ 59 | "$set": bson.M{ 60 | "index": video.Index, 61 | }, 62 | }) 63 | if err != nil { 64 | return err 65 | } 66 | } 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /handlers/admin/about.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/backpulse/core/database" 8 | "github.com/backpulse/core/models" 9 | "github.com/backpulse/core/utils" 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | // GetAbout : return about content of site 14 | func GetAbout(w http.ResponseWriter, r *http.Request) { 15 | vars := mux.Vars(r) 16 | name := vars["name"] 17 | 18 | site, _ := database.GetSiteByName(name) 19 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 20 | 21 | if !utils.IsAuthorized(site, user) { 22 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 23 | return 24 | } 25 | 26 | aboutContent, err := database.GetAbout(site.ID) 27 | 28 | if err != nil { 29 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 30 | return 31 | } 32 | utils.RespondWithJSON(w, http.StatusOK, "success", aboutContent) 33 | return 34 | } 35 | 36 | // UpdateAbout : update about content of site 37 | func UpdateAbout(w http.ResponseWriter, r *http.Request) { 38 | vars := mux.Vars(r) 39 | name := vars["name"] 40 | 41 | var aboutContent models.AboutContent 42 | /* Parse json to models.User */ 43 | err := json.NewDecoder(r.Body).Decode(&aboutContent) 44 | if err != nil { 45 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 46 | return 47 | } 48 | 49 | /* Check correct owner */ 50 | site, _ := database.GetSiteByName(name) 51 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 52 | 53 | if !utils.IsAuthorized(site, user) { 54 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 55 | return 56 | } 57 | 58 | if len(aboutContent.Name) > 60 { 59 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_too_long", nil) 60 | return 61 | } 62 | 63 | aboutContent.SiteID = site.ID 64 | aboutContent.OwnerID = site.OwnerID 65 | 66 | database.UpdateAbout(site.ID, aboutContent) 67 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /routes/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/http" 5 | 6 | clientHandlers "github.com/backpulse/core/handlers/client" 7 | "github.com/backpulse/core/utils" 8 | "github.com/gorilla/mux" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | // HandleClient : handle client routes 13 | func HandleClient(r *mux.Router) { 14 | 15 | greetings := func(w http.ResponseWriter, r *http.Request) { 16 | utils.RespondWithJSON(w, http.StatusOK, "Welcome to the API", bson.M{ 17 | "wrapper": "https://github.com/backpulse/wrapper", 18 | }) 19 | } 20 | 21 | r.HandleFunc("", greetings).Methods("GET") 22 | r.HandleFunc("/", greetings).Methods("GET") 23 | 24 | r.HandleFunc("/contact", clientHandlers.GetContact).Methods("GET") 25 | 26 | r.HandleFunc("/about", clientHandlers.GetAbout).Methods("GET") 27 | 28 | r.HandleFunc("/galleries/default", clientHandlers.GetDefaultGallery).Methods("GET") 29 | r.HandleFunc("/galleries", clientHandlers.GetGalleries).Methods("GET") 30 | r.HandleFunc("/gallery/{short_id}", clientHandlers.GetGallery).Methods("GET") 31 | 32 | r.HandleFunc("/photos", clientHandlers.GetPhotos).Methods("GET") 33 | r.HandleFunc("/photos/{id}", clientHandlers.GetPhoto).Methods("GET") 34 | 35 | r.HandleFunc("/projects", clientHandlers.GetProjects).Methods("GET") 36 | r.HandleFunc("/projects/{short_id}", clientHandlers.GetProject).Methods("GET") 37 | 38 | r.HandleFunc("/articles", clientHandlers.GetArticles).Methods("GET") 39 | r.HandleFunc("/articles/{short_id}", clientHandlers.GetArticle).Methods("GET") 40 | 41 | r.HandleFunc("/videogroups", clientHandlers.GetVideoGroups).Methods("GET") 42 | r.HandleFunc("/videogroups/{short_id}", clientHandlers.GetVideoGroup).Methods("GET") 43 | 44 | r.HandleFunc("/videos/{short_id}", clientHandlers.GetVideo).Methods("GET") 45 | 46 | r.HandleFunc("/albums", clientHandlers.GetAlbums).Methods("GET") 47 | r.HandleFunc("/albums/{short_id}", clientHandlers.GetAlbum).Methods("GET") 48 | 49 | r.HandleFunc("/tracks/{short_id}", clientHandlers.GetTrack).Methods("GET") 50 | 51 | } 52 | -------------------------------------------------------------------------------- /database/projects.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/backpulse/core/models" 7 | "github.com/teris-io/shortid" 8 | "gopkg.in/mgo.v2/bson" 9 | ) 10 | 11 | // GetProject : return project using shortid 12 | func GetProjectByShortID(shortID string) (models.Project, error) { 13 | var project models.Project 14 | err := DB.C(projectsCollection).Find(bson.M{ 15 | "short_id": shortID, 16 | }).One(&project) 17 | 18 | project.Title = getDefaultProjectTitle(project) 19 | 20 | return project, err 21 | } 22 | 23 | // GetProject : return project using shortid 24 | func GetProject(id bson.ObjectId) (models.Project, error) { 25 | var project models.Project 26 | err := DB.C(projectsCollection).FindId(id).One(&project) 27 | 28 | project.Title = getDefaultProjectTitle(project) 29 | 30 | return project, err 31 | } 32 | 33 | // RemoveProject : remove project from db 34 | func RemoveProject(id bson.ObjectId) error { 35 | err := DB.C(projectsCollection).RemoveId(id) 36 | return err 37 | } 38 | 39 | // GetProjects : return projects of site 40 | func GetProjects(id bson.ObjectId) ([]models.Project, error) { 41 | var projects []models.Project 42 | err := DB.C(projectsCollection).Find(bson.M{ 43 | "site_id": id, 44 | }).All(&projects) 45 | 46 | for i := range projects { 47 | title := getDefaultProjectTitle(projects[i]) 48 | projects[i].Title = title 49 | } 50 | 51 | return projects, err 52 | } 53 | 54 | // UpsertProject Update or insert project 55 | func UpsertProject(project models.Project) error { 56 | if project.ID == "" { 57 | project.CreatedAt = time.Now() 58 | project.ID = bson.NewObjectId() 59 | project.ShortID, _ = shortid.Generate() 60 | } 61 | project.UpdatedAt = time.Now() 62 | _, err := DB.C(projectsCollection).UpsertId(project.ID, bson.M{ 63 | "$set": project, 64 | }) 65 | return err 66 | } 67 | 68 | // GetDefaultProjectTitle : return default project title 69 | func getDefaultProjectTitle(project models.Project) string { 70 | for i := range project.Titles { 71 | if project.Titles[i].LanguageCode == "en" { 72 | return project.Titles[i].Content 73 | } 74 | } 75 | return project.Titles[0].Content 76 | } 77 | -------------------------------------------------------------------------------- /database/tracks.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/backpulse/core/models" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | // AddTrack : create track object in db 11 | func AddTrack(track models.Track) error { 12 | track.UpdatedAt = time.Now() 13 | track.CreatedAt = time.Now() 14 | err := DB.C(tracksCollection).Insert(track) 15 | return err 16 | } 17 | 18 | // GetTrack : return specific track by ObjectID 19 | func GetTrack(trackID bson.ObjectId) (models.Track, error) { 20 | var track models.Track 21 | err := DB.C(tracksCollection).FindId(trackID).One(&track) 22 | return track, err 23 | } 24 | 25 | // GetTrack : return specific track by shortID 26 | func GetTrackByShortID(shortID string) (models.Track, error) { 27 | var track models.Track 28 | err := DB.C(tracksCollection).Find(bson.M{ 29 | "short_id": shortID, 30 | }).One(&track) 31 | return track, err 32 | } 33 | 34 | // GetAlbumTracks : return array of track for specific album 35 | func GetAlbumTracks(id bson.ObjectId) ([]models.Track, error) { 36 | var tracks []models.Track 37 | err := DB.C(tracksCollection).Find(bson.M{ 38 | "album_id": id, 39 | }).All(&tracks) 40 | return tracks, err 41 | } 42 | 43 | // UpdateTrack : update track informations (title, url, image) 44 | func UpdateTrack(id bson.ObjectId, track models.Track) error { 45 | err := DB.C(tracksCollection).UpdateId(id, bson.M{ 46 | "$set": bson.M{ 47 | "title": track.Title, 48 | "url": track.URL, 49 | "image": track.Image, 50 | "content": track.Content, 51 | }, 52 | }) 53 | return err 54 | } 55 | 56 | // RemoveTrack : delete track from db 57 | func RemoveTrack(id bson.ObjectId) error { 58 | err := DB.C(tracksCollection).RemoveId(id) 59 | return err 60 | } 61 | 62 | // UpdateTracksIndexes : update order of tracks 63 | func UpdateTracksIndexes(siteID bson.ObjectId, tracks []models.Track) error { 64 | for _, track := range tracks { 65 | err := DB.C(tracksCollection).Update(bson.M{ 66 | "site_id": siteID, 67 | "_id": track.ID, 68 | }, bson.M{ 69 | "$set": bson.M{ 70 | "index": track.Index, 71 | }, 72 | }) 73 | if err != nil { 74 | return err 75 | } 76 | } 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /database/albums.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/backpulse/core/models" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | // AddAlbum : create album object in db 11 | func AddAlbum(album models.Album) error { 12 | album.UpdatedAt = time.Now() 13 | album.CreatedAt = time.Now() 14 | err := DB.C(albumsCollection).Insert(album) 15 | return err 16 | } 17 | 18 | // UpdateAlbum : update album (title, cover, description) 19 | func UpdateAlbum(id bson.ObjectId, album models.Album) error { 20 | err := DB.C(albumsCollection).UpdateId(id, bson.M{ 21 | "$set": bson.M{ 22 | "title": album.Title, 23 | "cover": album.Cover, 24 | "description": album.Description, 25 | }, 26 | }) 27 | return err 28 | } 29 | 30 | // GetAlbum : return specific album by ObjectID 31 | func GetAlbum(ID bson.ObjectId) (models.Album, error) { 32 | var album models.Album 33 | err := DB.C(albumsCollection).FindId(ID).One(&album) 34 | 35 | tracks, err := GetAlbumTracks(album.ID) 36 | if err != nil { 37 | return models.Album{}, nil 38 | } 39 | album.Tracks = tracks 40 | 41 | return album, err 42 | } 43 | 44 | // GetAlbumByShortID : return specific album by short_id 45 | func GetAlbumByShortID(id string) (models.Album, error) { 46 | var album models.Album 47 | err := DB.C(albumsCollection).Find(bson.M{ 48 | "short_id": id, 49 | }).One(&album) 50 | 51 | tracks, err := GetAlbumTracks(album.ID) 52 | if err != nil { 53 | return models.Album{}, nil 54 | } 55 | album.Tracks = tracks 56 | 57 | return album, err 58 | } 59 | 60 | // GetAlbums : return array of album for specific site 61 | func GetAlbums(siteID bson.ObjectId) ([]models.Album, error) { 62 | var albums []models.Album 63 | err := DB.C(albumsCollection).Find(bson.M{ 64 | "site_id": siteID, 65 | }).All(&albums) 66 | 67 | return albums, err 68 | } 69 | 70 | // RemoveAlbum : delete album object from db 71 | func RemoveAlbum(id bson.ObjectId) error { 72 | err := DB.C(albumsCollection).RemoveId(id) 73 | return err 74 | } 75 | 76 | // UpdateAlbumsIndexes : change order of albums 77 | func UpdateAlbumsIndexes(siteID bson.ObjectId, albums []models.Album) error { 78 | for _, album := range albums { 79 | err := DB.C(albumsCollection).Update(bson.M{ 80 | "site_id": siteID, 81 | "_id": album.ID, 82 | }, bson.M{ 83 | "$set": bson.M{ 84 | "index": album.Index, 85 | }, 86 | }) 87 | if err != nil { 88 | return err 89 | } 90 | } 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /database/videogroups.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/backpulse/core/models" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | // AddVideoGroup : add video group to db 11 | func AddVideoGroup(videoGroup models.VideoGroup) error { 12 | videoGroup.UpdatedAt = time.Now() 13 | videoGroup.CreatedAt = time.Now() 14 | err := DB.C(videoGroupsCollection).Insert(videoGroup) 15 | return err 16 | } 17 | 18 | // UpdateVideoGroup : update video group informations (title, image) 19 | func UpdateVideoGroup(id bson.ObjectId, group models.VideoGroup) error { 20 | err := DB.C(videoGroupsCollection).UpdateId(id, bson.M{ 21 | "$set": bson.M{ 22 | "title": group.Title, 23 | "image": group.Image, 24 | }, 25 | }) 26 | return err 27 | } 28 | 29 | // GetVideoGroup : return specific video group by ObjectID 30 | func GetVideoGroup(ID bson.ObjectId) (models.VideoGroup, error) { 31 | var videoGroup models.VideoGroup 32 | err := DB.C(videoGroupsCollection).FindId(ID).One(&videoGroup) 33 | 34 | videos, err := GetGroupVideos(videoGroup.ID) 35 | if err != nil { 36 | return models.VideoGroup{}, nil 37 | } 38 | videoGroup.Videos = videos 39 | 40 | return videoGroup, err 41 | } 42 | 43 | // GetVideoGroupByShortID : return specific video group by short_id 44 | func GetVideoGroupByShortID(id string) (models.VideoGroup, error) { 45 | var videoGroup models.VideoGroup 46 | err := DB.C(videoGroupsCollection).Find(bson.M{ 47 | "short_id": id, 48 | }).One(&videoGroup) 49 | 50 | videos, err := GetGroupVideos(videoGroup.ID) 51 | if err != nil { 52 | return models.VideoGroup{}, nil 53 | } 54 | videoGroup.Videos = videos 55 | 56 | return videoGroup, err 57 | } 58 | 59 | // GetVideoGroups : Return array of videogroup from site 60 | func GetVideoGroups(siteID bson.ObjectId) ([]models.VideoGroup, error) { 61 | var videoGroups []models.VideoGroup 62 | err := DB.C(videoGroupsCollection).Find(bson.M{ 63 | "site_id": siteID, 64 | }).All(&videoGroups) 65 | 66 | return videoGroups, err 67 | } 68 | 69 | // RemoveVideoGroup : remove video group from db 70 | func RemoveVideoGroup(id bson.ObjectId) error { 71 | err := DB.C(videoGroupsCollection).RemoveId(id) 72 | return err 73 | } 74 | 75 | // UpdateVideoGroupsIndexes : Update order of video groups 76 | func UpdateVideoGroupsIndexes(siteID bson.ObjectId, groups []models.VideoGroup) error { 77 | for _, g := range groups { 78 | err := DB.C(videoGroupsCollection).Update(bson.M{ 79 | "site_id": siteID, 80 | "_id": g.ID, 81 | }, bson.M{ 82 | "$set": bson.M{ 83 | "index": g.Index, 84 | }, 85 | }) 86 | if err != nil { 87 | return err 88 | } 89 | } 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /handlers/client/galleries.go: -------------------------------------------------------------------------------- 1 | package clienthandlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "gopkg.in/mgo.v2/bson" 7 | 8 | "github.com/backpulse/core/database" 9 | "github.com/backpulse/core/utils" 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | // GetGallery : return specific gallery 14 | func GetGallery(w http.ResponseWriter, r *http.Request) { 15 | vars := mux.Vars(r) 16 | id := vars["short_id"] 17 | gallery, err := database.GetGalleryByShortID(id) 18 | if err != nil { 19 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 20 | return 21 | } 22 | utils.RespondWithJSON(w, http.StatusOK, "success", gallery) 23 | return 24 | } 25 | 26 | func GetPhotos(w http.ResponseWriter, r *http.Request) { 27 | vars := mux.Vars(r) 28 | name := vars["name"] 29 | site, err := database.GetSiteByName(name) 30 | if err != nil { 31 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 32 | return 33 | } 34 | photos, _ := database.GetSitePhotos(site.ID) 35 | utils.RespondWithJSON(w, http.StatusOK, "success", photos) 36 | return 37 | } 38 | 39 | func GetPhoto(w http.ResponseWriter, r *http.Request) { 40 | vars := mux.Vars(r) 41 | id := bson.ObjectIdHex(vars["id"]) 42 | photo, err := database.GetPhotoByID(id) 43 | if err != nil { 44 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 45 | return 46 | } 47 | utils.RespondWithJSON(w, http.StatusOK, "success", photo) 48 | return 49 | } 50 | 51 | // GetGalleries : return array of galleries 52 | func GetGalleries(w http.ResponseWriter, r *http.Request) { 53 | vars := mux.Vars(r) 54 | name := vars["name"] 55 | 56 | site, err := database.GetSiteByName(name) 57 | if err != nil { 58 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 59 | return 60 | } 61 | 62 | galleries, err := database.GetGalleries(site.ID) 63 | if err != nil { 64 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 65 | return 66 | } 67 | 68 | for i := range galleries { 69 | photos, _ := database.GetGalleryPhotos(galleries[i].ID) 70 | galleries[i].Photos = photos 71 | } 72 | 73 | utils.RespondWithJSON(w, http.StatusOK, "success", galleries) 74 | return 75 | } 76 | 77 | // GetHomeGallery : return home gallery of site 78 | func GetDefaultGallery(w http.ResponseWriter, r *http.Request) { 79 | vars := mux.Vars(r) 80 | name := vars["name"] 81 | site, err := database.GetSiteByName(name) 82 | if err != nil { 83 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 84 | return 85 | } 86 | gallery, err := database.GetDefaultGallery(site.ID) 87 | if err != nil { 88 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 89 | return 90 | } 91 | utils.RespondWithJSON(w, http.StatusOK, "success", gallery) 92 | return 93 | 94 | } 95 | -------------------------------------------------------------------------------- /handlers/admin/contact.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/backpulse/core/database" 9 | "github.com/backpulse/core/models" 10 | "github.com/backpulse/core/utils" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | // GetContact : return contact data about site 15 | func GetContact(w http.ResponseWriter, r *http.Request) { 16 | vars := mux.Vars(r) 17 | name := vars["name"] 18 | 19 | site, _ := database.GetSiteByName(name) 20 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 21 | 22 | if !utils.IsAuthorized(site, user) { 23 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 24 | return 25 | } 26 | 27 | contact, err := database.GetContact(site.ID) 28 | if err != nil { 29 | log.Println(err) 30 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 31 | return 32 | } 33 | utils.RespondWithJSON(w, http.StatusOK, "success", contact) 34 | return 35 | } 36 | 37 | // UpdateContact : update contact data 38 | func UpdateContact(w http.ResponseWriter, r *http.Request) { 39 | vars := mux.Vars(r) 40 | name := vars["name"] 41 | 42 | var contactContent models.ContactContent 43 | /* Parse json to models.User */ 44 | err := json.NewDecoder(r.Body).Decode(&contactContent) 45 | if err != nil { 46 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 47 | return 48 | } 49 | 50 | /* Check correct owner */ 51 | site, _ := database.GetSiteByName(name) 52 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 53 | 54 | if !utils.IsAuthorized(site, user) { 55 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 56 | return 57 | } 58 | 59 | if len(contactContent.Name) > 60 { 60 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_too_long", nil) 61 | return 62 | } 63 | if len(contactContent.Phone) > 25 { 64 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "phone_too_long", nil) 65 | return 66 | } 67 | if len(contactContent.Email) > 100 { 68 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "email_too_long", nil) 69 | return 70 | } 71 | if len(contactContent.Address) > 150 { 72 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "address_too_long", nil) 73 | return 74 | } 75 | if len(contactContent.FacebookURL) > 125 { 76 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "facebook_url_too_long", nil) 77 | return 78 | } 79 | if len(contactContent.InstagramURL) > 125 { 80 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "instagram_url_too_long", nil) 81 | return 82 | } 83 | if len(contactContent.TwitterURL) > 125 { 84 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "twitter_url_too_long", nil) 85 | return 86 | } 87 | 88 | contactContent.SiteID = site.ID 89 | contactContent.OwnerID = site.OwnerID 90 | 91 | database.UpdateContact(site.ID, contactContent) 92 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 93 | 94 | return 95 | 96 | } 97 | -------------------------------------------------------------------------------- /database/photos.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/backpulse/core/models" 8 | "gopkg.in/mgo.v2/bson" 9 | ) 10 | 11 | // InsertPhoto : insert photo in db 12 | func InsertPhoto(photo models.Photo) (models.Photo, error) { 13 | err := DB.C(photosCollection).Insert(photo) 14 | return photo, err 15 | } 16 | 17 | // GetGalleryPhotos return photos of gallery 18 | func GetGalleryPhotos(id bson.ObjectId) ([]models.Photo, error) { 19 | var photos []models.Photo 20 | err := DB.C(photosCollection).Find(bson.M{ 21 | // "is_gallery": true, 22 | "gallery_id": id, 23 | }).All(&photos) 24 | return photos, err 25 | } 26 | 27 | // DeletePhotos : delete multiple photos from db 28 | func DeletePhotos(userID bson.ObjectId, ids []bson.ObjectId) error { 29 | log.Println(ids) 30 | _, err := DB.C(photosCollection).RemoveAll(bson.M{ 31 | "owner_id": userID, 32 | "_id": bson.M{ 33 | "$in": ids, 34 | }, 35 | }) 36 | return err 37 | } 38 | 39 | func GetPhotoByID(id bson.ObjectId) (models.Photo, error) { 40 | var photo models.Photo 41 | err := DB.C(photosCollection).FindId(id).One(&photo) 42 | return photo, err 43 | } 44 | 45 | // GetPhotos : return array of photos 46 | func GetPhotos(userID bson.ObjectId, ids []bson.ObjectId) ([]models.Photo, error) { 47 | var photos []models.Photo 48 | err := DB.C(photosCollection).Find(bson.M{ 49 | "owner_id": userID, 50 | "_id": bson.M{ 51 | "$in": ids, 52 | }, 53 | }).All(&photos) 54 | return photos, err 55 | } 56 | 57 | // GetSitePhotos : return photos from site 58 | func GetSitePhotos(id bson.ObjectId) ([]models.Photo, error) { 59 | var photos []models.Photo 60 | err := DB.C(photosCollection).Find(bson.M{ 61 | "site_id": id, 62 | }).All(&photos) 63 | return photos, err 64 | } 65 | 66 | // UpdatePhotosIndexes : update order of photos 67 | func UpdatePhotosIndexes(gallery models.Gallery, photos []models.Photo) error { 68 | for _, photo := range photos { 69 | err := DB.C(photosCollection).Update(bson.M{ 70 | "site_id": gallery.SiteID, 71 | "_id": photo.ID, 72 | }, bson.M{ 73 | "$set": bson.M{ 74 | "index": photo.Index, 75 | }, 76 | }) 77 | if err != nil { 78 | return err 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | // UpdatePhoto updates title & content 85 | func UpdatePhoto(id bson.ObjectId, photo models.Photo) error { 86 | update := bson.M{ 87 | "title": photo.Title, 88 | "content": photo.Content, 89 | "gallery_id": photo.GalleryID, 90 | "url": photo.URL, 91 | } 92 | err := DB.C(photosCollection).UpdateId(id, bson.M{ 93 | "$set": update, 94 | }) 95 | return err 96 | } 97 | 98 | func CreatePhoto(photo models.Photo) (models.Photo, error) { 99 | photo.ID = bson.NewObjectId() 100 | photo.CreatedAt = time.Now() 101 | err := DB.C(photosCollection).Insert(photo) 102 | return photo, err 103 | } 104 | 105 | func UpdatePhotoURL(id bson.ObjectId, url string) error { 106 | err := DB.C(photosCollection).UpdateId(id, bson.M{ 107 | "$set": bson.M{ 108 | "url": url, 109 | }, 110 | }) 111 | return err 112 | } 113 | -------------------------------------------------------------------------------- /utils/googlecloud.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "log" 7 | "mime/multipart" 8 | "os" 9 | 10 | "cloud.google.com/go/storage" 11 | "github.com/backpulse/core/models" 12 | "github.com/sirupsen/logrus" 13 | "google.golang.org/api/option" 14 | "gopkg.in/mgo.v2/bson" 15 | ) 16 | 17 | var ctx context.Context 18 | var gClient *storage.Client 19 | 20 | func InitGoogleCloud() { 21 | ctx = context.Background() 22 | c, err := GetGoogleCloudClient(ctx) 23 | if err != nil { 24 | logrus.Infoln("Google Cloud: ERROR", err) 25 | } 26 | gClient = c 27 | logrus.Infoln("Google Cloud: OK") 28 | return 29 | } 30 | 31 | // GetGoogleCloudClient : Return google cloud client 32 | func GetGoogleCloudClient(ctx context.Context) (*storage.Client, error) { 33 | 34 | // Use ./google_credentials.json by default 35 | if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" { 36 | os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "./google_credentials.json") 37 | client, err := storage.NewClient(ctx) 38 | return client, err 39 | } 40 | 41 | // Use env variables 42 | client, err := storage.NewClient(ctx, option.WithCredentialsJSON([]byte(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")))) 43 | return client, err 44 | } 45 | 46 | // UpdateFilename : update filename of a file 47 | func UpdateFilename(fileID bson.ObjectId, filename string) error { 48 | config := GetConfig() 49 | bucketName := config.BucketName 50 | 51 | ctx := context.Background() 52 | 53 | bucket := gClient.Bucket(bucketName) 54 | object := bucket.Object(fileID.Hex()) 55 | 56 | log.Print(fileID.Hex()) 57 | _, err := object.Update(ctx, storage.ObjectAttrsToUpdate{ 58 | ContentDisposition: "inline; filename=\"" + filename + "\"", 59 | }) 60 | log.Print(err) 61 | return err 62 | } 63 | 64 | // UploadFile : upload file to google cloud 65 | func UploadFile(file multipart.File, fileName string) (bson.ObjectId, error) { 66 | 67 | config := GetConfig() 68 | bucketName := config.BucketName 69 | 70 | ctx := context.Background() 71 | 72 | objectID := bson.NewObjectId() 73 | 74 | bucket := gClient.Bucket(bucketName) 75 | object := bucket.Object(objectID.Hex()) 76 | 77 | object.ACL().Set(ctx, storage.AllUsers, storage.RoleReader) 78 | 79 | wc := object.NewWriter(ctx) 80 | _, err := io.Copy(wc, file) 81 | if err != nil { 82 | log.Println(err) 83 | return "", err 84 | } 85 | 86 | err = wc.Close() 87 | if err != nil { 88 | log.Println(err) 89 | return "", err 90 | } 91 | update, err := object.Update(ctx, storage.ObjectAttrsToUpdate{ 92 | ContentDisposition: "inline; filename=\"" + fileName + "\"", 93 | }) 94 | log.Println("update", update) 95 | log.Println("error", err) 96 | 97 | return objectID, nil 98 | } 99 | 100 | // RemoveGoogleCloudPhotos : remove photos from google cloud to save space 101 | func RemoveGoogleCloudPhotos(photos []models.Photo) error { 102 | config := GetConfig() 103 | bucketName := config.BucketName 104 | 105 | ctx := context.Background() 106 | 107 | bucket := gClient.Bucket(bucketName) 108 | for _, photo := range photos { 109 | log.Println(photo.ID.Hex()) 110 | object := bucket.Object(photo.ID.Hex()) 111 | err := object.Delete(ctx) 112 | if err != nil { 113 | log.Println(err) 114 | 115 | return err 116 | } 117 | } 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![forthebadge](https://forthebadge.com/images/badges/made-with-go.svg)](https://forthebadge.com) 2 | [![forthebadge](https://forthebadge.com/images/badges/powered-by-netflix.svg)](https://forthebadge.com) 3 | 4 | [![Donate](https://img.shields.io/badge/Donate-Crypto-blue.svg)](https://commerce.coinbase.com/checkout/b4d64264-dda8-41d0-9f15-0843f969fa79) 5 | [![Donate](https://img.shields.io/badge/Donate-Patreon-orange.svg)](https://www.patreon.com/backpulse) 6 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/aureleoules) 7 | 8 | ![Backpulse](https://files.backpulse.io/backpulse.png#cache2 "Backpulse.io") 9 | 10 | # Backpulse core 11 | Backpulse is an API Based / Headless CMS. 12 | Your site's content is accessible directly via our RESTful API, on any web framework and any device. 13 | 14 | ## Installation 15 | With a correctly configured Go toolchain: 16 | ```bash 17 | go get github.com/backpulse/core 18 | ``` 19 | 20 | ## Build&Run from source 21 | With a correctly configured(go version >=go1.11) Go toolchain: 22 | ```bash 23 | git clone https://github.com/backpulse/core 24 | cd core 25 | make build 26 | ./backpulse 27 | ``` 28 | 29 | ## Docker Build&Run 30 | ```bash 31 | docker build -t . 32 | docker run -d --link :mongodb 33 | ``` 34 | or docker run in custom environment 35 | ```bash 36 | docker run -d \ 37 | --link :mongodb \ 38 | --env MONGODB_URI=mongodb://mongodb:27017 \ 39 | --env DATABASE=backpulse \ 40 | 41 | ``` 42 | 43 | ## Usage 44 | First, you need to create a config.json using the `config.json.template` file. 45 | * **URI** : MongoDB server address (_mongodb://..._) 46 | * **Database** : MongoDB database name 47 | * **Secret** : A secret key to encrypt JWT 48 | * **GmailAddress** : A gmail address if you wish to send confirmation emails 49 | * **GmailPassword** : The password associated with the gmail address obviously 50 | * **StripeKey** : Your Stripe Key if you wish to integrate Stripe 51 | * **BucketName** : Your Google Cloud Storage Bucket's name to store user files (images, binaries, plain text...) 52 | 53 | You can also pass all these variables as environment variables: 54 | * MONGODB_URI 55 | * DATABASE 56 | * SECRET 57 | * GMAIL_ADDRESS 58 | * GMAIL_PASSWORD 59 | * STRIPE_KEY 60 | * BUCKET_NAME 61 | 62 | 63 | **Note**: If a `config.json` file is found, it will override environment variables. 64 | 65 | Then, you need to get your Google Service Account Key: 66 | * Go to this [page](https://console.cloud.google.com/apis/credentials/serviceaccountkey). 67 | * Create a new account with the Project -> Owner role. 68 | * Download your private key as JSON. 69 | * Move it to the root of this project. 70 | * Rename it `google_credentials.json`. 71 | 72 | You can also pass the content of this json file as an environment variable: 73 | 74 | GOOGLE_APPLICATION_CREDENTIALS = `{"type": "service_account", "project_id": "projectID", ...}` 75 | 76 | You're all set to run **Backpulse**! 77 | ```bash 78 | go build -o backpulse && backpulse 79 | ``` 80 | 81 | **Note**: By default Backpulse runs on port 8000, but can be overridden with the `PORT` environment variable. 82 | 83 | ## Contributing 84 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 85 | 86 | 87 | ## License 88 | [MIT](https://github.com/backpulse/core/blob/master/LICENSE) © [Aurèle Oulès](https://www.aureleoules.com) -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at contact@backpulse.io. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /handlers/admin/projects.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/backpulse/core/database" 9 | "github.com/backpulse/core/models" 10 | "github.com/backpulse/core/utils" 11 | "github.com/gorilla/mux" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | // DeleteProject : remove project from db 16 | func DeleteProject(w http.ResponseWriter, r *http.Request) { 17 | vars := mux.Vars(r) 18 | id := vars["id"] 19 | 20 | project, _ := database.GetProject(bson.ObjectIdHex(id)) 21 | site, _ := database.GetSiteByID(project.SiteID) 22 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 23 | if !utils.IsAuthorized(site, user) { 24 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 25 | return 26 | } 27 | 28 | if project.SiteID != site.ID { 29 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 30 | return 31 | } 32 | 33 | err := database.RemoveProject(bson.ObjectIdHex(id)) 34 | if err != nil { 35 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 36 | return 37 | } 38 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 39 | return 40 | } 41 | 42 | // GetProject : return project using shortid 43 | func GetProject(w http.ResponseWriter, r *http.Request) { 44 | vars := mux.Vars(r) 45 | id := vars["id"] 46 | 47 | project, err := database.GetProject(bson.ObjectIdHex(id)) 48 | 49 | site, _ := database.GetSiteByID(project.SiteID) 50 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 51 | 52 | if !utils.IsAuthorized(site, user) { 53 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 54 | return 55 | } 56 | 57 | if err != nil { 58 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 59 | return 60 | } 61 | 62 | if project.OwnerID != site.OwnerID { 63 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 64 | return 65 | } 66 | 67 | utils.RespondWithJSON(w, http.StatusOK, "success", project) 68 | return 69 | } 70 | 71 | // GetProjects : return array of projects of site 72 | func GetProjects(w http.ResponseWriter, r *http.Request) { 73 | vars := mux.Vars(r) 74 | name := vars["name"] 75 | 76 | site, _ := database.GetSiteByName(name) 77 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 78 | if !utils.IsAuthorized(site, user) { 79 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 80 | return 81 | } 82 | 83 | projects, err := database.GetProjects(site.ID) 84 | if err != nil { 85 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 86 | return 87 | } 88 | 89 | utils.RespondWithJSON(w, http.StatusOK, "success", projects) 90 | return 91 | } 92 | 93 | // UpdateProject : Update or insert new project 94 | func UpdateProject(w http.ResponseWriter, r *http.Request) { 95 | vars := mux.Vars(r) 96 | name := vars["name"] 97 | 98 | site, _ := database.GetSiteByName(name) 99 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 100 | if !utils.IsAuthorized(site, user) { 101 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 102 | return 103 | } 104 | 105 | var project models.Project 106 | /* Parse json to models.Project */ 107 | err := json.NewDecoder(r.Body).Decode(&project) 108 | if err != nil { 109 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 110 | return 111 | } 112 | 113 | if len(project.Titles) < 1 { 114 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "title_required", nil) 115 | return 116 | } 117 | 118 | if len(project.URL) > 200 { 119 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "url_too_long", nil) 120 | return 121 | } 122 | 123 | project.SiteID = site.ID 124 | project.OwnerID = site.OwnerID 125 | err = database.UpsertProject(project) 126 | if err != nil { 127 | log.Println(err) 128 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 129 | return 130 | } 131 | 132 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 133 | return 134 | } 135 | -------------------------------------------------------------------------------- /handlers/admin/articles.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/backpulse/core/database" 9 | "github.com/backpulse/core/models" 10 | "github.com/backpulse/core/utils" 11 | "github.com/gorilla/mux" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | // GetArticles : return array of article of site 16 | func GetArticles(w http.ResponseWriter, r *http.Request) { 17 | vars := mux.Vars(r) 18 | name := vars["name"] 19 | 20 | site, _ := database.GetSiteByName(name) 21 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 22 | 23 | if !utils.IsAuthorized(site, user) { 24 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 25 | return 26 | } 27 | 28 | articles, err := database.GetArticles(site.ID) 29 | if err != nil { 30 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 31 | return 32 | } 33 | 34 | utils.RespondWithJSON(w, http.StatusOK, "success", articles) 35 | return 36 | } 37 | 38 | // GetArticle : return specific article 39 | func GetArticle(w http.ResponseWriter, r *http.Request) { 40 | vars := mux.Vars(r) 41 | name := vars["name"] 42 | id := vars["id"] 43 | 44 | site, _ := database.GetSiteByName(name) 45 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 46 | 47 | if !utils.IsAuthorized(site, user) { 48 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 49 | return 50 | } 51 | article, err := database.GetArticle(bson.ObjectIdHex(id)) 52 | if err != nil { 53 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 54 | return 55 | } 56 | 57 | if article.SiteID != site.ID { 58 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 59 | return 60 | } 61 | 62 | utils.RespondWithJSON(w, http.StatusOK, "success", article) 63 | return 64 | 65 | } 66 | 67 | // UpdateArticle : create/update article 68 | func UpdateArticle(w http.ResponseWriter, r *http.Request) { 69 | vars := mux.Vars(r) 70 | name := vars["name"] 71 | 72 | site, _ := database.GetSiteByName(name) 73 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 74 | 75 | if !utils.IsAuthorized(site, user) { 76 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 77 | return 78 | } 79 | 80 | var article models.Article 81 | /* Parse json to models.Project */ 82 | err := json.NewDecoder(r.Body).Decode(&article) 83 | if err != nil { 84 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 85 | return 86 | } 87 | 88 | if len(article.Title) < 1 { 89 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "title_required", nil) 90 | return 91 | } 92 | 93 | if len(article.Title) > 200 { 94 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "title_too_long", nil) 95 | return 96 | } 97 | 98 | if len(article.Content) < 1 { 99 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "content_required", nil) 100 | return 101 | } 102 | 103 | if article.ID != "" { 104 | a, _ := database.GetArticle(article.ID) 105 | if a.SiteID != site.ID { 106 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 107 | return 108 | } 109 | } 110 | 111 | article.SiteID = site.ID 112 | article.OwnerID = site.OwnerID 113 | article, err = database.UpsertArticle(article) 114 | if err != nil { 115 | log.Println(err) 116 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 117 | return 118 | } 119 | 120 | utils.RespondWithJSON(w, http.StatusOK, "success", article) 121 | return 122 | 123 | } 124 | 125 | // DeleteArticle : remove article from db 126 | func DeleteArticle(w http.ResponseWriter, r *http.Request) { 127 | vars := mux.Vars(r) 128 | name := vars["name"] 129 | id := vars["id"] 130 | 131 | site, _ := database.GetSiteByName(name) 132 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 133 | 134 | if !utils.IsAuthorized(site, user) { 135 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 136 | return 137 | } 138 | 139 | article, err := database.GetArticle(bson.ObjectIdHex(id)) 140 | if err != nil { 141 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 142 | return 143 | } 144 | 145 | if article.SiteID != site.ID { 146 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 147 | return 148 | } 149 | 150 | err = database.RemoveArticle(article.ID) 151 | if err != nil { 152 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 153 | return 154 | } 155 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 156 | return 157 | } 158 | -------------------------------------------------------------------------------- /handlers/admin/files.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "log" 5 | "math" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "github.com/backpulse/core/database" 11 | "github.com/backpulse/core/models" 12 | "github.com/backpulse/core/utils" 13 | "github.com/gorilla/mux" 14 | "gopkg.in/mgo.v2/bson" 15 | ) 16 | 17 | // GetFiles : Return array of files for site 18 | func GetFiles(w http.ResponseWriter, r *http.Request) { 19 | vars := mux.Vars(r) 20 | name := vars["name"] 21 | 22 | site, _ := database.GetSiteByName(name) 23 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 24 | 25 | if !utils.IsAuthorized(site, user) { 26 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 27 | return 28 | } 29 | 30 | files, err := database.GetSiteFiles(site.ID) 31 | if err != nil { 32 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 33 | return 34 | } 35 | utils.RespondWithJSON(w, http.StatusOK, "success", files) 36 | return 37 | } 38 | 39 | // UpdateFilename : update filename on gcloud 40 | func UpdateFilename(w http.ResponseWriter, r *http.Request) { 41 | vars := mux.Vars(r) 42 | name := vars["name"] 43 | id := vars["id"] 44 | filename := vars["filename"] 45 | 46 | site, _ := database.GetSiteByName(name) 47 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 48 | 49 | if !utils.IsAuthorized(site, user) { 50 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 51 | return 52 | } 53 | 54 | if len(filename) < 1 { 55 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_required", nil) 56 | return 57 | } 58 | 59 | err := database.UpdateFilename(bson.ObjectIdHex(id), filename, site.ID) 60 | if err != nil { 61 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 62 | return 63 | } 64 | 65 | err = utils.UpdateFilename(bson.ObjectIdHex(id), filename) 66 | if err != nil { 67 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 68 | return 69 | } 70 | 71 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 72 | return 73 | } 74 | 75 | // UploadFile : upload file to gcloud 76 | func UploadFile(w http.ResponseWriter, r *http.Request) { 77 | /* Get file from Client */ 78 | file, header, err := r.FormFile("file") 79 | if err != nil { 80 | utils.RespondWithJSON(w, http.StatusInternalServerError, err.Error(), nil) 81 | return 82 | } 83 | defer file.Close() 84 | 85 | vars := mux.Vars(r) 86 | name := vars["name"] 87 | 88 | site, _ := database.GetSiteByName(name) 89 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 90 | 91 | if !utils.IsAuthorized(site, user) { 92 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 93 | return 94 | } 95 | 96 | id, err := utils.UploadFile(file, header.Filename) 97 | if err != nil { 98 | log.Println(err) 99 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 100 | return 101 | } 102 | 103 | config := utils.GetConfig() 104 | 105 | fileObject := models.File{ 106 | ID: id, 107 | Name: header.Filename, 108 | OwnerID: site.OwnerID, 109 | SiteID: site.ID, 110 | Size: math.Round(float64(header.Size)/10000) / 100, 111 | CreatedAt: time.Now(), 112 | Type: header.Header.Get("Content-Type"), 113 | URL: config.BucketPubURL + "/" + id.Hex(), 114 | } 115 | 116 | err = database.InsertFile(fileObject) 117 | if err != nil { 118 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 119 | return 120 | } 121 | utils.RespondWithJSON(w, http.StatusOK, "success", fileObject) 122 | return 123 | } 124 | 125 | // DeleteFiles : remove files from db & gcloud 126 | func DeleteFiles(w http.ResponseWriter, r *http.Request) { 127 | vars := mux.Vars(r) 128 | name := vars["name"] 129 | fileIds := vars["ids"] 130 | 131 | stringIDsArray := strings.Split(fileIds, ",") 132 | var ids []bson.ObjectId 133 | 134 | for _, id := range stringIDsArray { 135 | if bson.IsObjectIdHex(id) { 136 | ids = append(ids, bson.ObjectIdHex(id)) 137 | } 138 | } 139 | 140 | site, _ := database.GetSiteByName(name) 141 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 142 | 143 | if !utils.IsAuthorized(site, user) { 144 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 145 | return 146 | } 147 | 148 | for _, id := range ids { 149 | err := database.DeleteFile(id, site.ID) 150 | if err != nil { 151 | log.Println(err) 152 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 153 | return 154 | } 155 | } 156 | 157 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 158 | return 159 | 160 | } 161 | -------------------------------------------------------------------------------- /database/galleries.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/backpulse/core/models" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | // CreateGallery : insert new gallery in db 11 | func CreateGallery(gallery models.Gallery) error { 12 | err := DB.C(galleriesCollection).Insert(gallery) 13 | return err 14 | } 15 | 16 | // SetDefaultGallery : set a default gallery (useful for a homepage gallery for example) 17 | func SetDefaultGallery(site models.Site, gallery models.Gallery) error { 18 | _, err := DB.C(galleriesCollection).UpdateAll(bson.M{ 19 | "site_id": site.ID, 20 | }, bson.M{ 21 | "$set": bson.M{ 22 | "default_gallery": false, 23 | }, 24 | }) 25 | if err != nil { 26 | return err 27 | } 28 | err = DB.C(galleriesCollection).UpdateId(gallery.ID, bson.M{ 29 | "$set": bson.M{ 30 | "default_gallery": true, 31 | }, 32 | }) 33 | return err 34 | } 35 | 36 | // UpdateGalleriesIndexes : Update galleries order 37 | func UpdateGalleriesIndexes(siteID bson.ObjectId, galleries []models.Gallery) error { 38 | for _, g := range galleries { 39 | err := DB.C(galleriesCollection).Update(bson.M{ 40 | "site_id": siteID, 41 | "_id": g.ID, 42 | }, bson.M{ 43 | "$set": bson.M{ 44 | "index": g.Index, 45 | }, 46 | }) 47 | if err != nil { 48 | return err 49 | } 50 | } 51 | return nil 52 | } 53 | 54 | // GetGallery return specific gallery 55 | func GetGalleryByShortID(shortID string) (models.Gallery, error) { 56 | var gallery models.Gallery 57 | err := DB.C(galleriesCollection).Find(bson.M{ 58 | "short_id": shortID, 59 | }).One(&gallery) 60 | 61 | if err != nil { 62 | return models.Gallery{}, err 63 | } 64 | 65 | photos, err := GetGalleryPhotos(gallery.ID) 66 | if err != nil { 67 | return models.Gallery{}, err 68 | } 69 | 70 | gallery.Photos = photos 71 | gallery.PreviewPhoto, _ = GetPhotoByID(gallery.PreviewPhotoID) 72 | gallery.Title = getDefaultGalleryTitle(gallery) 73 | 74 | return gallery, err 75 | } 76 | 77 | // GetGallery return specific gallery 78 | func GetGallery(id bson.ObjectId) (models.Gallery, error) { 79 | var gallery models.Gallery 80 | err := DB.C(galleriesCollection).FindId(id).One(&gallery) 81 | 82 | if err != nil { 83 | return models.Gallery{}, err 84 | } 85 | 86 | photos, err := GetGalleryPhotos(gallery.ID) 87 | if err != nil { 88 | return models.Gallery{}, err 89 | } 90 | 91 | gallery.Photos = photos 92 | gallery.PreviewPhoto, _ = GetPhotoByID(gallery.PreviewPhotoID) 93 | gallery.Title = getDefaultGalleryTitle(gallery) 94 | 95 | return gallery, err 96 | } 97 | 98 | // SetGalleryPreview : set a gallery preview image 99 | func SetGalleryPreview(gallery models.Gallery, photo models.Photo) error { 100 | err := DB.C(galleriesCollection).UpdateId(gallery.ID, bson.M{ 101 | "$set": bson.M{ 102 | "preview_photo_id": photo.ID, 103 | }, 104 | }) 105 | return err 106 | } 107 | 108 | // GetDefaultGallery return default gallery 109 | func GetDefaultGallery(id bson.ObjectId) (models.Gallery, error) { 110 | var gallery models.Gallery 111 | err := DB.C(galleriesCollection).Find(bson.M{ 112 | "site_id": id, 113 | "default_gallery": true, 114 | }).One(&gallery) 115 | 116 | if err != nil { 117 | return models.Gallery{}, err 118 | } 119 | 120 | photos, err := GetGalleryPhotos(gallery.ID) 121 | if err != nil { 122 | return models.Gallery{}, err 123 | } 124 | 125 | gallery.PreviewPhoto, _ = GetPhotoByID(gallery.PreviewPhotoID) 126 | 127 | gallery.Photos = photos 128 | 129 | gallery.Title = getDefaultGalleryTitle(gallery) 130 | 131 | return gallery, err 132 | } 133 | 134 | // GetGalleries return site's galleries 135 | func GetGalleries(id bson.ObjectId) ([]models.Gallery, error) { 136 | var galleries []models.Gallery 137 | err := DB.C(galleriesCollection).Find(bson.M{ 138 | "site_id": id, 139 | }).All(&galleries) 140 | 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | for i := range galleries { 146 | title := getDefaultGalleryTitle(galleries[i]) 147 | galleries[i].Title = title 148 | galleries[i].PreviewPhoto, _ = GetPhotoByID(galleries[i].PreviewPhotoID) 149 | } 150 | 151 | return galleries, err 152 | } 153 | 154 | // GetDefaultGalleryTitle : return default gallery title 155 | func getDefaultGalleryTitle(gallery models.Gallery) string { 156 | for i := range gallery.Titles { 157 | if gallery.Titles[i].LanguageCode == "en" { 158 | return gallery.Titles[i].Content 159 | } 160 | } 161 | return gallery.Titles[0].Content 162 | } 163 | 164 | // UpdateGallery update gallery 165 | func UpdateGallery(id bson.ObjectId, gallery models.Gallery) error { 166 | err := DB.C(galleriesCollection).UpdateId(id, bson.M{ 167 | "$set": bson.M{ 168 | "titles": gallery.Titles, 169 | "descriptions": gallery.Descriptions, 170 | "updated_at": time.Now(), 171 | }, 172 | }) 173 | return err 174 | } 175 | 176 | // DeleteGallery remove gallery from db 177 | func DeleteGallery(id bson.ObjectId) error { 178 | err := DB.C(galleriesCollection).RemoveId(id) 179 | if err != nil { 180 | return err 181 | } 182 | _, err = DB.C(photosCollection).RemoveAll(bson.M{ 183 | "gallery_id": id, 184 | }) 185 | return err 186 | } 187 | -------------------------------------------------------------------------------- /database/users.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/asaskevich/govalidator" 8 | "github.com/backpulse/core/models" 9 | stripe "github.com/stripe/stripe-go" 10 | "gopkg.in/mgo.v2/bson" 11 | ) 12 | 13 | // GetUserByEmail : return user by email 14 | func GetUserByEmail(email string) (models.User, error) { 15 | var user models.User 16 | err := DB.C(usersCollection).Find(bson.M{ 17 | "email": email, 18 | }).One(&user) 19 | return user, err 20 | } 21 | 22 | // RemoveUserSubscription : remove pro subscription from user 23 | func RemoveUserSubscription(user models.User) error { 24 | err := DB.C(usersCollection).UpdateId(user.ID, bson.M{ 25 | "$set": bson.M{ 26 | "active_subscription_id": "", 27 | "professional": false, 28 | }, 29 | }) 30 | return err 31 | } 32 | 33 | // SetUserPro : add pro subscription to user 34 | func SetUserPro(id bson.ObjectId, customer *stripe.Customer, subscription *stripe.Subscription) error { 35 | err := DB.C(usersCollection).UpdateId(id, bson.M{ 36 | "$set": bson.M{ 37 | "professional": true, 38 | "stripe_id": customer.ID, 39 | "active_subscription_id": subscription.ID, 40 | }, 41 | }) 42 | return err 43 | } 44 | 45 | //UpdateUser : update user data 46 | func UpdateUser(user models.User, updateUser models.User) (models.User, error) { 47 | err := DB.C(usersCollection).UpdateId(user.ID, bson.M{ 48 | "$set": bson.M{ 49 | "fullname": updateUser.FullName, 50 | "updated_at": time.Now(), 51 | "address": updateUser.Address, 52 | "country": updateUser.Country, 53 | "zip": updateUser.ZIP, 54 | "city": updateUser.City, 55 | "state": updateUser.State, 56 | }, 57 | }) 58 | if err != nil { 59 | return models.User{}, err 60 | } 61 | var newUser models.User 62 | err = DB.C(usersCollection).FindId(user.ID).One(&newUser) 63 | if err != nil { 64 | return models.User{}, err 65 | } 66 | return newUser, nil 67 | } 68 | 69 | //InsertEmailVerification insert email verification object in db 70 | func InsertEmailVerification(verification models.EmailVerification) error { 71 | err := DB.C(emailVerificationsCollection).Insert(verification) 72 | return err 73 | } 74 | 75 | //GetVerificationByID : get email verification by id 76 | func GetVerificationByID(id bson.ObjectId) (models.EmailVerification, error) { 77 | var verification models.EmailVerification 78 | err := DB.C(emailVerificationsCollection).FindId(id).One(&verification) 79 | return verification, err 80 | } 81 | 82 | //DeleteVerification : remove verification from db 83 | func DeleteVerification(verification models.EmailVerification) error { 84 | err := DB.C(emailVerificationsCollection).RemoveId(verification.ID) 85 | return err 86 | } 87 | 88 | //VerifyUser : verify user 89 | func VerifyUser(user models.User, verification models.EmailVerification) error { 90 | err := DB.C(usersCollection).UpdateId(user.ID, bson.M{ 91 | "$set": bson.M{ 92 | "email": verification.Email, 93 | "email_verified": true, 94 | }, 95 | }) 96 | if err != nil { 97 | return err 98 | } 99 | err = DeleteVerification(verification) 100 | return err 101 | } 102 | 103 | // RemoveUser : remove user from db 104 | func RemoveUser(id bson.ObjectId) error { 105 | err := DB.C(usersCollection).RemoveId(id) 106 | return err 107 | } 108 | 109 | // UpdateUserPassword : update user password, password is of course hashed 110 | func UpdateUserPassword(id bson.ObjectId, password string) error { 111 | err := DB.C(usersCollection).UpdateId(id, bson.M{ 112 | "$set": bson.M{ 113 | "password": password, 114 | }, 115 | }) 116 | return err 117 | } 118 | 119 | //AddUser inserts user into Database 120 | func AddUser(user models.User) (models.User, error) { 121 | id := bson.NewObjectId() 122 | user.ID = id 123 | user.CreatedAt = time.Now() 124 | err := DB.C(usersCollection).Insert(&user) 125 | return user, err 126 | } 127 | 128 | //GetUser get user by username or email 129 | func GetUser(email string) (models.User, error) { 130 | var user models.User 131 | err := DB.C(usersCollection).Find(bson.M{ 132 | "email": email, 133 | }).One(&user) 134 | return user, err 135 | } 136 | 137 | //GetUserByID : returns user object with id 138 | func GetUserByID(id bson.ObjectId) (models.User, error) { 139 | var user models.User 140 | err := DB.C(usersCollection).FindId(id).One(&user) 141 | return user, err 142 | } 143 | 144 | //IsEmailRegistered checks if email already exists in db 145 | func IsEmailRegistered(email string) bool { 146 | var user models.User 147 | _ = DB.C(usersCollection).Find(bson.M{ 148 | "email": email, 149 | }).One(&user) 150 | 151 | return user.Email == email 152 | } 153 | 154 | //CreateVerification : insert a verification status in db 155 | func CreateVerification(user models.User) (models.EmailVerification, error) { 156 | id := bson.NewObjectId() 157 | verification := models.EmailVerification{ 158 | UserID: user.ID, 159 | Email: user.Email, 160 | ExpireAt: time.Now().Add(time.Hour * time.Duration(24)), 161 | ID: id, 162 | } 163 | err := InsertEmailVerification(verification) 164 | return verification, err 165 | } 166 | 167 | //VerifyEmail : check email format and whether it's been assigned already or not 168 | func VerifyEmail(email string, r *http.Request) (bool, string) { 169 | /* Check email */ 170 | if !govalidator.IsEmail(email) { 171 | return false, "invalid_email" 172 | } 173 | 174 | /* Check if email was used already */ 175 | if IsEmailRegistered(email) { 176 | return false, "email_exists" 177 | } 178 | return true, "" 179 | } 180 | -------------------------------------------------------------------------------- /handlers/admin/tracks.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/backpulse/core/database" 9 | "github.com/backpulse/core/models" 10 | "github.com/backpulse/core/utils" 11 | "github.com/gorilla/mux" 12 | "github.com/teris-io/shortid" 13 | "gopkg.in/mgo.v2/bson" 14 | ) 15 | 16 | // AddTrack : add track to album 17 | func AddTrack(w http.ResponseWriter, r *http.Request) { 18 | vars := mux.Vars(r) 19 | name := vars["name"] 20 | albumid := vars["albumid"] 21 | 22 | site, _ := database.GetSiteByName(name) 23 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 24 | 25 | if !utils.IsAuthorized(site, user) { 26 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 27 | return 28 | } 29 | 30 | album, err := database.GetAlbum(bson.ObjectIdHex(albumid)) 31 | if err != nil { 32 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 33 | return 34 | } 35 | 36 | if album.SiteID != site.ID { 37 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 38 | return 39 | } 40 | 41 | var track models.Track 42 | /* Parse json to models.Track */ 43 | err = json.NewDecoder(r.Body).Decode(&track) 44 | if err != nil { 45 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 46 | return 47 | } 48 | 49 | track.AlbumID = bson.ObjectIdHex(albumid) 50 | 51 | if track.AlbumID == "" { 52 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "album_id_required", nil) 53 | return 54 | } 55 | 56 | track.SiteID = site.ID 57 | track.OwnerID = site.OwnerID 58 | track.ShortID, _ = shortid.Generate() 59 | track.ID = bson.NewObjectId() 60 | 61 | tracks, _ := database.GetAlbumTracks(track.AlbumID) 62 | track.Index = len(tracks) 63 | 64 | err = database.AddTrack(track) 65 | if err != nil { 66 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 67 | return 68 | } 69 | 70 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 71 | return 72 | } 73 | 74 | // UpdateTrack : update track informations 75 | func UpdateTrack(w http.ResponseWriter, r *http.Request) { 76 | vars := mux.Vars(r) 77 | name := vars["name"] 78 | id := vars["id"] 79 | 80 | site, _ := database.GetSiteByName(name) 81 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 82 | 83 | if !utils.IsAuthorized(site, user) { 84 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 85 | return 86 | } 87 | 88 | var track models.Track 89 | /* Parse json to models.Track */ 90 | err := json.NewDecoder(r.Body).Decode(&track) 91 | if err != nil { 92 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 93 | return 94 | } 95 | 96 | err = database.UpdateTrack(bson.ObjectIdHex(id), track) 97 | if err != nil { 98 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 99 | return 100 | } 101 | 102 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 103 | return 104 | } 105 | 106 | // DeleteTrack : remove track from album 107 | func DeleteTrack(w http.ResponseWriter, r *http.Request) { 108 | vars := mux.Vars(r) 109 | name := vars["name"] 110 | id := vars["id"] 111 | 112 | site, _ := database.GetSiteByName(name) 113 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 114 | 115 | if !utils.IsAuthorized(site, user) { 116 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 117 | return 118 | } 119 | 120 | track, err := database.GetTrack(bson.ObjectIdHex(id)) 121 | if err != nil { 122 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 123 | return 124 | } 125 | 126 | err = database.RemoveTrack(track.ID) 127 | if err != nil { 128 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 129 | return 130 | } 131 | 132 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 133 | return 134 | } 135 | 136 | // GetTrack : get specific track 137 | func GetTrack(w http.ResponseWriter, r *http.Request) { 138 | vars := mux.Vars(r) 139 | name := vars["name"] 140 | id := vars["id"] 141 | 142 | site, _ := database.GetSiteByName(name) 143 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 144 | 145 | if !utils.IsAuthorized(site, user) { 146 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 147 | return 148 | } 149 | 150 | track, err := database.GetTrack(bson.ObjectIdHex(id)) 151 | if err != nil { 152 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 153 | return 154 | } 155 | 156 | utils.RespondWithJSON(w, http.StatusOK, "success", track) 157 | return 158 | } 159 | 160 | // UpdateTracksIndexes : update tracks order 161 | func UpdateTracksIndexes(w http.ResponseWriter, r *http.Request) { 162 | vars := mux.Vars(r) 163 | siteName := vars["name"] 164 | 165 | site, _ := database.GetSiteByName(siteName) 166 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 167 | 168 | if !utils.IsAuthorized(site, user) { 169 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 170 | return 171 | } 172 | 173 | var tracks []models.Track 174 | /* Parse json to models.Gallery */ 175 | err := json.NewDecoder(r.Body).Decode(&tracks) 176 | if err != nil { 177 | log.Print(err) 178 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 179 | return 180 | } 181 | 182 | err = database.UpdateTracksIndexes(site.ID, tracks) 183 | if err != nil { 184 | log.Print(err) 185 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 186 | return 187 | } 188 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 189 | return 190 | } 191 | -------------------------------------------------------------------------------- /handlers/admin/videogroups.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/backpulse/core/database" 9 | "github.com/backpulse/core/models" 10 | "github.com/backpulse/core/utils" 11 | "github.com/gorilla/mux" 12 | "github.com/teris-io/shortid" 13 | "gopkg.in/mgo.v2/bson" 14 | ) 15 | 16 | func CreateVideoGroup(w http.ResponseWriter, r *http.Request) { 17 | vars := mux.Vars(r) 18 | name := vars["name"] 19 | 20 | site, _ := database.GetSiteByName(name) 21 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 22 | 23 | if !utils.IsAuthorized(site, user) { 24 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 25 | return 26 | } 27 | 28 | var videoGroup models.VideoGroup 29 | /* Parse json to models.Project */ 30 | err := json.NewDecoder(r.Body).Decode(&videoGroup) 31 | if err != nil { 32 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 33 | return 34 | } 35 | 36 | videoGroup.ShortID, _ = shortid.Generate() 37 | videoGroup.SiteID = site.ID 38 | videoGroup.OwnerID = site.OwnerID 39 | videoGroup.ID = bson.NewObjectId() 40 | 41 | groups, _ := database.GetVideoGroups(site.ID) 42 | videoGroup.Index = len(groups) 43 | 44 | err = database.AddVideoGroup(videoGroup) 45 | if err != nil { 46 | log.Print(err) 47 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 48 | return 49 | } 50 | 51 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 52 | return 53 | } 54 | 55 | func GetVideoGroups(w http.ResponseWriter, r *http.Request) { 56 | vars := mux.Vars(r) 57 | name := vars["name"] 58 | 59 | site, _ := database.GetSiteByName(name) 60 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 61 | 62 | if !utils.IsAuthorized(site, user) { 63 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 64 | return 65 | } 66 | 67 | videoGroups, err := database.GetVideoGroups(site.ID) 68 | if err != nil { 69 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 70 | return 71 | } 72 | 73 | utils.RespondWithJSON(w, http.StatusOK, "success", videoGroups) 74 | return 75 | } 76 | 77 | func GetVideoGroup(w http.ResponseWriter, r *http.Request) { 78 | vars := mux.Vars(r) 79 | name := vars["name"] 80 | id := vars["id"] 81 | 82 | site, _ := database.GetSiteByName(name) 83 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 84 | 85 | if !utils.IsAuthorized(site, user) { 86 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 87 | return 88 | } 89 | 90 | videoGroup, err := database.GetVideoGroup(bson.ObjectIdHex(id)) 91 | if err != nil { 92 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 93 | return 94 | } 95 | 96 | if videoGroup.SiteID != site.ID { 97 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 98 | return 99 | } 100 | 101 | videos, _ := database.GetGroupVideos(videoGroup.ID) 102 | videoGroup.Videos = videos 103 | 104 | utils.RespondWithJSON(w, http.StatusOK, "success", videoGroup) 105 | return 106 | } 107 | func DeleteVideoGroup(w http.ResponseWriter, r *http.Request) { 108 | vars := mux.Vars(r) 109 | name := vars["name"] 110 | id := vars["id"] 111 | 112 | site, _ := database.GetSiteByName(name) 113 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 114 | 115 | if !utils.IsAuthorized(site, user) { 116 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 117 | return 118 | } 119 | 120 | videoGroup, err := database.GetVideoGroup(bson.ObjectIdHex(id)) 121 | if videoGroup.SiteID != site.ID { 122 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 123 | return 124 | } 125 | 126 | for _, video := range videoGroup.Videos { 127 | database.RemoveVideo(video.ID) 128 | } 129 | 130 | err = database.RemoveVideoGroup(videoGroup.ID) 131 | if err != nil { 132 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 133 | return 134 | } 135 | 136 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 137 | return 138 | } 139 | 140 | // UpdateVideoGroup : rename & change image 141 | func UpdateVideoGroup(w http.ResponseWriter, r *http.Request) { 142 | vars := mux.Vars(r) 143 | siteName := vars["name"] 144 | id := vars["id"] 145 | 146 | site, _ := database.GetSiteByName(siteName) 147 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 148 | 149 | if !utils.IsAuthorized(site, user) { 150 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 151 | return 152 | } 153 | var group models.VideoGroup 154 | /* Parse json to models.Gallery */ 155 | err := json.NewDecoder(r.Body).Decode(&group) 156 | if err != nil { 157 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 158 | return 159 | } 160 | 161 | g, err := database.GetVideoGroup(bson.ObjectIdHex(id)) 162 | if err != nil { 163 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 164 | return 165 | } 166 | if g.SiteID != site.ID { 167 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 168 | return 169 | } 170 | 171 | err = database.UpdateVideoGroup(bson.ObjectIdHex(id), group) 172 | if err != nil { 173 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 174 | return 175 | } 176 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 177 | return 178 | } 179 | 180 | func UpdateVideoGroupsIndexes(w http.ResponseWriter, r *http.Request) { 181 | vars := mux.Vars(r) 182 | siteName := vars["name"] 183 | 184 | site, _ := database.GetSiteByName(siteName) 185 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 186 | 187 | if !utils.IsAuthorized(site, user) { 188 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 189 | return 190 | } 191 | 192 | var groups []models.VideoGroup 193 | /* Parse json to models.Gallery */ 194 | err := json.NewDecoder(r.Body).Decode(&groups) 195 | if err != nil { 196 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 197 | return 198 | } 199 | 200 | err = database.UpdateVideoGroupsIndexes(site.ID, groups) 201 | if err != nil { 202 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 203 | return 204 | } 205 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 206 | return 207 | } 208 | -------------------------------------------------------------------------------- /handlers/admin/albums.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/backpulse/core/database" 9 | "github.com/backpulse/core/models" 10 | "github.com/backpulse/core/utils" 11 | "github.com/gorilla/mux" 12 | "github.com/teris-io/shortid" 13 | "gopkg.in/mgo.v2/bson" 14 | ) 15 | 16 | // CreateAlbum : create new album 17 | func CreateAlbum(w http.ResponseWriter, r *http.Request) { 18 | vars := mux.Vars(r) 19 | name := vars["name"] 20 | 21 | site, _ := database.GetSiteByName(name) 22 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 23 | 24 | if !utils.IsAuthorized(site, user) { 25 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 26 | return 27 | } 28 | 29 | var album models.Album 30 | /* Parse json to models.Album */ 31 | err := json.NewDecoder(r.Body).Decode(&album) 32 | if err != nil { 33 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 34 | return 35 | } 36 | 37 | album.ShortID, _ = shortid.Generate() 38 | album.SiteID = site.ID 39 | album.OwnerID = site.OwnerID 40 | album.ID = bson.NewObjectId() 41 | 42 | albums, _ := database.GetAlbums(site.ID) 43 | album.Index = len(albums) 44 | 45 | err = database.AddAlbum(album) 46 | if err != nil { 47 | log.Print(err) 48 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 49 | return 50 | } 51 | 52 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 53 | return 54 | } 55 | 56 | // GetAlbums : return albums of site 57 | func GetAlbums(w http.ResponseWriter, r *http.Request) { 58 | vars := mux.Vars(r) 59 | name := vars["name"] 60 | 61 | site, _ := database.GetSiteByName(name) 62 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 63 | 64 | if !utils.IsAuthorized(site, user) { 65 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 66 | return 67 | } 68 | 69 | albums, err := database.GetAlbums(site.ID) 70 | if err != nil { 71 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 72 | return 73 | } 74 | 75 | utils.RespondWithJSON(w, http.StatusOK, "success", albums) 76 | return 77 | } 78 | 79 | // GetAlbum : return specific album 80 | func GetAlbum(w http.ResponseWriter, r *http.Request) { 81 | vars := mux.Vars(r) 82 | name := vars["name"] 83 | id := vars["id"] 84 | 85 | site, _ := database.GetSiteByName(name) 86 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 87 | 88 | if !utils.IsAuthorized(site, user) { 89 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 90 | return 91 | } 92 | 93 | album, err := database.GetAlbum(bson.ObjectIdHex(id)) 94 | if err != nil { 95 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 96 | return 97 | } 98 | 99 | if album.SiteID != site.ID { 100 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 101 | return 102 | } 103 | 104 | tracks, _ := database.GetAlbumTracks(album.ID) 105 | album.Tracks = tracks 106 | 107 | utils.RespondWithJSON(w, http.StatusOK, "success", album) 108 | return 109 | } 110 | 111 | // DeleteAlbum : remove album from db 112 | func DeleteAlbum(w http.ResponseWriter, r *http.Request) { 113 | vars := mux.Vars(r) 114 | name := vars["name"] 115 | id := vars["id"] 116 | 117 | site, _ := database.GetSiteByName(name) 118 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 119 | 120 | if !utils.IsAuthorized(site, user) { 121 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 122 | return 123 | } 124 | 125 | album, err := database.GetAlbum(bson.ObjectIdHex(id)) 126 | if album.SiteID != site.ID { 127 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 128 | return 129 | } 130 | 131 | for _, track := range album.Tracks { 132 | database.RemoveTrack(track.ID) 133 | } 134 | 135 | err = database.RemoveAlbum(album.ID) 136 | if err != nil { 137 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 138 | return 139 | } 140 | 141 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 142 | return 143 | } 144 | 145 | // UpdateAlbum : rename & change image 146 | func UpdateAlbum(w http.ResponseWriter, r *http.Request) { 147 | vars := mux.Vars(r) 148 | siteName := vars["name"] 149 | id := vars["id"] 150 | 151 | site, _ := database.GetSiteByName(siteName) 152 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 153 | 154 | if !utils.IsAuthorized(site, user) { 155 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 156 | return 157 | } 158 | var album models.Album 159 | /* Parse json to models.Album */ 160 | err := json.NewDecoder(r.Body).Decode(&album) 161 | if err != nil { 162 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 163 | return 164 | } 165 | 166 | a, err := database.GetAlbum(bson.ObjectIdHex(id)) 167 | if err != nil { 168 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 169 | return 170 | } 171 | if a.SiteID != site.ID { 172 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 173 | return 174 | } 175 | 176 | err = database.UpdateAlbum(bson.ObjectIdHex(id), album) 177 | if err != nil { 178 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 179 | return 180 | } 181 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 182 | return 183 | } 184 | 185 | // UpdateAlbumsIndxes : update order of albums 186 | func UpdateAlbumsIndexes(w http.ResponseWriter, r *http.Request) { 187 | vars := mux.Vars(r) 188 | siteName := vars["name"] 189 | 190 | site, _ := database.GetSiteByName(siteName) 191 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 192 | 193 | if !utils.IsAuthorized(site, user) { 194 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 195 | return 196 | } 197 | 198 | var albums []models.Album 199 | /* Parse json to models.Album */ 200 | err := json.NewDecoder(r.Body).Decode(&albums) 201 | if err != nil { 202 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 203 | return 204 | } 205 | 206 | err = database.UpdateAlbumsIndexes(site.ID, albums) 207 | if err != nil { 208 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 209 | return 210 | } 211 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 212 | return 213 | } 214 | -------------------------------------------------------------------------------- /handlers/admin/videos.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "regexp" 8 | 9 | "github.com/backpulse/core/database" 10 | "github.com/backpulse/core/models" 11 | "github.com/backpulse/core/utils" 12 | "github.com/gorilla/mux" 13 | "github.com/teris-io/shortid" 14 | "gopkg.in/mgo.v2/bson" 15 | ) 16 | 17 | // AddVideo : add video to video group 18 | func AddVideo(w http.ResponseWriter, r *http.Request) { 19 | vars := mux.Vars(r) 20 | name := vars["name"] 21 | groupid := vars["groupid"] 22 | 23 | site, _ := database.GetSiteByName(name) 24 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 25 | 26 | if !utils.IsAuthorized(site, user) { 27 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 28 | return 29 | } 30 | 31 | group, err := database.GetVideoGroup(bson.ObjectIdHex(groupid)) 32 | if err != nil { 33 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 34 | return 35 | } 36 | 37 | if group.SiteID != site.ID { 38 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 39 | return 40 | } 41 | 42 | var video models.Video 43 | /* Parse json to models.Project */ 44 | err = json.NewDecoder(r.Body).Decode(&video) 45 | if err != nil { 46 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 47 | return 48 | } 49 | 50 | video.VideoGroupID = bson.ObjectIdHex(groupid) 51 | 52 | if len(video.YouTubeURL) < 1 { 53 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "url_required", nil) 54 | return 55 | } 56 | match, _ := regexp.MatchString("^(http(s)?:\\/\\/)?((w){3}.)?youtu(be|.be)?(\\.com)?\\/.+", video.YouTubeURL) 57 | if !match { 58 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "url_required", nil) 59 | return 60 | } 61 | 62 | if video.VideoGroupID == "" { 63 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "video_group_required", nil) 64 | return 65 | } 66 | 67 | video.SiteID = site.ID 68 | video.OwnerID = site.OwnerID 69 | video.ShortID, _ = shortid.Generate() 70 | video.ID = bson.NewObjectId() 71 | 72 | videos, _ := database.GetGroupVideos(video.VideoGroupID) 73 | video.Index = len(videos) 74 | 75 | err = database.AddVideo(video) 76 | if err != nil { 77 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 78 | return 79 | } 80 | 81 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 82 | return 83 | } 84 | 85 | // UpdateVideo : update informations of video (title, url) 86 | func UpdateVideo(w http.ResponseWriter, r *http.Request) { 87 | vars := mux.Vars(r) 88 | name := vars["name"] 89 | id := vars["id"] 90 | 91 | site, _ := database.GetSiteByName(name) 92 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 93 | 94 | if !utils.IsAuthorized(site, user) { 95 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 96 | return 97 | } 98 | 99 | var video models.Video 100 | /* Parse json to models.Project */ 101 | err := json.NewDecoder(r.Body).Decode(&video) 102 | if err != nil { 103 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 104 | return 105 | } 106 | 107 | if len(video.YouTubeURL) < 1 { 108 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "url_required", nil) 109 | return 110 | } 111 | match, _ := regexp.MatchString("^(http(s)?:\\/\\/)?((w){3}.)?youtu(be|.be)?(\\.com)?\\/.+", video.YouTubeURL) 112 | if !match { 113 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "url_required", nil) 114 | return 115 | } 116 | 117 | err = database.UpdateVideo(bson.ObjectIdHex(id), video) 118 | if err != nil { 119 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 120 | return 121 | } 122 | 123 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 124 | return 125 | } 126 | 127 | // DeleteVideo : remove video from video group 128 | func DeleteVideo(w http.ResponseWriter, r *http.Request) { 129 | vars := mux.Vars(r) 130 | name := vars["name"] 131 | id := vars["id"] 132 | 133 | site, _ := database.GetSiteByName(name) 134 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 135 | 136 | if !utils.IsAuthorized(site, user) { 137 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 138 | return 139 | } 140 | 141 | video, err := database.GetVideo(bson.ObjectIdHex(id)) 142 | if err != nil { 143 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 144 | return 145 | } 146 | 147 | err = database.RemoveVideo(video.ID) 148 | if err != nil { 149 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 150 | return 151 | } 152 | 153 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 154 | return 155 | } 156 | 157 | // GetVideo : get specific video 158 | func GetVideo(w http.ResponseWriter, r *http.Request) { 159 | vars := mux.Vars(r) 160 | name := vars["name"] 161 | id := vars["id"] 162 | 163 | site, _ := database.GetSiteByName(name) 164 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 165 | 166 | if !utils.IsAuthorized(site, user) { 167 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 168 | return 169 | } 170 | 171 | video, err := database.GetVideo(bson.ObjectIdHex(id)) 172 | if err != nil { 173 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 174 | return 175 | } 176 | 177 | utils.RespondWithJSON(w, http.StatusOK, "success", video) 178 | return 179 | } 180 | 181 | // UpdateVideoIndexes : update order of videos in video group 182 | func UpdateVideosIndexes(w http.ResponseWriter, r *http.Request) { 183 | vars := mux.Vars(r) 184 | siteName := vars["name"] 185 | 186 | site, _ := database.GetSiteByName(siteName) 187 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 188 | 189 | if !utils.IsAuthorized(site, user) { 190 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 191 | return 192 | } 193 | 194 | var videos []models.Video 195 | /* Parse json to models.Gallery */ 196 | err := json.NewDecoder(r.Body).Decode(&videos) 197 | if err != nil { 198 | log.Print(err) 199 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 200 | return 201 | } 202 | 203 | err = database.UpdateVideosIndexes(site.ID, videos) 204 | if err != nil { 205 | log.Print(err) 206 | 207 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 208 | return 209 | } 210 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 211 | return 212 | } 213 | -------------------------------------------------------------------------------- /routes/client/documentation.md: -------------------------------------------------------------------------------- 1 | ## Backpulse API 2 | 3 | This is the official Backpulse API. 4 | API Endpoint: 5 | ``` 6 | https://api.backpulse.io/:sitename 7 | ``` 8 | #### Successful request 9 | Example of a successful request. 10 | ```json 11 | { 12 | "status": "success", 13 | "code": 200, 14 | "message": "success", 15 | "payload": {...} 16 | } 17 | ``` 18 | 19 | #### Unsuccessful request 20 | Exemple of 404 error. 21 | ```json 22 | { 23 | "status": "error", 24 | "code": 404, 25 | "message": "not_found", 26 | "payload": null 27 | } 28 | ``` 29 | 30 | ### Models 31 | Examples of the API models. 32 | 33 | #### Gallery 34 | ```json 35 | { 36 | "short_id": "ef855uQmR", 37 | "site_name": "mysite", 38 | "title": string, // English title 39 | "titles": []Translation, 40 | "descriptions": []Translation, 41 | "photos": []Photo, 42 | "created_at": Date, 43 | "updated_at": Date 44 | } 45 | ``` 46 | 47 | #### Project 48 | ```json 49 | { 50 | "short_id": "NzFVjUwig", 51 | "site_name": "aureleoules", 52 | "title": string, // English title 53 | "titles": []Translation, 54 | "descriptions": []Translation, 55 | "url": string, 56 | "created_at": Date, 57 | "updated_at": Date 58 | } 59 | ``` 60 | 61 | #### Translation 62 | ```json 63 | { 64 | "language_name": "English", 65 | "language_code": "en", 66 | "content": string 67 | } 68 | ``` 69 | #### Photo 70 | ```json 71 | { 72 | "url": "https://res.cloudinary.com/zygtpradi/image/authenticated/x--3BfHaB4O--/v1547280893/le6bd7z918vc8qas1s.jpg", 73 | "width": 1920, 74 | "height": 1080, 75 | "format": "jpg", 76 | "index": 0, 77 | "created_at": Date 78 | } 79 | ``` 80 | 81 | #### 82 | 83 | 84 | ## Routes 85 | List of all the API routes. 86 | 87 | ### Galleries 88 | 89 | Lists all galleries. 90 | 91 | ```endpoint 92 | GET /galleries 93 | ``` 94 | 95 | #### Example request 96 | 97 | ```curl 98 | $ curl https://api.backpulse.io/mysite/galleries 99 | ``` 100 | 101 | #### Example response 102 | 103 | ```json 104 | { 105 | "status": "success", 106 | "code": 200, 107 | "message": "success", 108 | "payload": [ 109 | { 110 | "short_id": "eS9u5RQmR", 111 | "site_name": "mysite", 112 | "title": string, 113 | "titles": []Translation, 114 | "descriptions": []Translation, 115 | "photos": []Photo, 116 | "created_at": Date, 117 | "updated_at": Date 118 | }, 119 | ... 120 | ] 121 | } 122 | ``` 123 | 124 | ### Gallery 125 | 126 | Get a specific gallery. 127 | 128 | ```endpoint 129 | GET /gallery/:short_id 130 | ``` 131 | Route parameter | Description 132 | --- | --- 133 | `short_id` | id of an existing gallery 134 | 135 | #### Example request 136 | 137 | ```curl 138 | $ curl https://api.backpulse.io/mysite/gallery/NzFVjUwig 139 | ``` 140 | 141 | #### Example response 142 | 143 | ```json 144 | { 145 | "status": "success", 146 | "code": 200, 147 | "message": "success", 148 | "payload": { 149 | "short_id": "eS9u5RQmR", 150 | "site_name": "mysite", 151 | "title": string, 152 | "titles": []Translation, 153 | "descriptions": []Translation, 154 | "photos": []Photo, 155 | "created_at": Date, 156 | "updated_at": Date 157 | } 158 | } 159 | ``` 160 | 161 | ### Default gallery 162 | 163 | Get the default gallery. 164 | 165 | ```endpoint 166 | GET /galleries/home 167 | ``` 168 | 169 | #### Example request 170 | 171 | ```curl 172 | $ curl https://api.backpulse.io/mysite/galleries/home 173 | ``` 174 | 175 | #### Example response 176 | 177 | ```json 178 | { 179 | "status": "success", 180 | "code": 200, 181 | "message": "success", 182 | "payload": { 183 | "short_id": "eS9u5RQmR", 184 | "site_name": "mysite", 185 | "title": string, 186 | "titles": []Translation, 187 | "descriptions": []Translation, 188 | "photos": []Photo, 189 | "created_at": Date, 190 | "updated_at": Date 191 | } 192 | } 193 | ``` 194 | 195 | ### Projects 196 | 197 | Lists all projects. 198 | 199 | ```endpoint 200 | GET /projects 201 | ``` 202 | 203 | #### Example request 204 | 205 | ```curl 206 | $ curl https://api.backpulse.io/mysite/projects 207 | ``` 208 | 209 | #### Example response 210 | 211 | ```json 212 | { 213 | "status": "success", 214 | "code": 200, 215 | "message": "success", 216 | "payload": [ 217 | { 218 | "short_id": "eS9u5RQmR", 219 | "site_name": "mysite", 220 | "title": string, 221 | "titles": []Translation, 222 | "descriptions": []Translation, 223 | "url": string, 224 | "created_at": Date, 225 | "updated_at": Date 226 | }, 227 | ... 228 | ] 229 | } 230 | ``` 231 | 232 | ### Project 233 | 234 | Get a specific project. 235 | 236 | ```endpoint 237 | GET /project/:short_id 238 | ``` 239 | Route parameter | Description 240 | --- | --- 241 | `short_id` | id of an existing project 242 | 243 | #### Example request 244 | 245 | ```curl 246 | $ curl https://api.backpulse.io/mysite/project/NzFVjUwig 247 | ``` 248 | 249 | #### Example response 250 | 251 | ```json 252 | { 253 | "status": "success", 254 | "code": 200, 255 | "message": "success", 256 | "payload": { 257 | "short_id": "eS9u5RQmR", 258 | "site_name": "mysite", 259 | "title": string, 260 | "titles": []Translation, 261 | "descriptions": []Translation, 262 | "url": string, 263 | "created_at": Date, 264 | "updated_at": Date 265 | } 266 | } 267 | ``` 268 | 269 | ### Contact 270 | 271 | Get contact informations. 272 | 273 | ```endpoint 274 | GET /contact 275 | ``` 276 | 277 | #### Example request 278 | 279 | ```curl 280 | $ curl https://api.backpulse.io/mysite/contact 281 | ``` 282 | 283 | #### Example response 284 | 285 | ```json 286 | { 287 | "status": "success", 288 | "code": 200, 289 | "message": "success", 290 | "payload": [ 291 | { 292 | "site_name": "aureleoules", 293 | "name": "John Doe", 294 | "phone": "202-555-0199", 295 | "email": "contact@backpulse.io", 296 | "address": "355 Yukon Lane\nOak Park, MI 48237", 297 | "facebook_url": "https://facebook.com/...", 298 | "instagram_url": "https://instagram.com/...", 299 | "twitter_url": "https://twitter.com/...", 300 | "custom_fields": []CustomField 301 | } 302 | ] 303 | } 304 | ``` 305 | 306 | ### About 307 | Get about informations. 308 | 309 | ```endpoint 310 | GET /about 311 | ``` 312 | 313 | #### Example request 314 | 315 | ```curl 316 | $ curl https://api.backpulse.io/mysite/about 317 | ``` 318 | 319 | #### Example response 320 | 321 | ```json 322 | { 323 | "status": "success", 324 | "code": 200, 325 | "message": "success", 326 | "payload": [ 327 | { 328 | "site_name": "aureleoules", 329 | "name": "John Doe", 330 | "descriptions": []Translation, 331 | "titles": []Translation 332 | } 333 | ] 334 | } 335 | ``` -------------------------------------------------------------------------------- /database/sites.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/backpulse/core/constants" 7 | "github.com/backpulse/core/models" 8 | "github.com/backpulse/core/utils" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | // GetSiteTotalSize : return site size in MB 13 | func GetSiteTotalSize(siteID bson.ObjectId) float64 { 14 | photos, _ := GetSitePhotos(siteID) 15 | 16 | var totalSize float64 17 | for _, photo := range photos { 18 | totalSize = totalSize + photo.Size 19 | } 20 | 21 | files, _ := GetSiteFiles(siteID) 22 | for _, file := range files { 23 | totalSize = totalSize + file.Size 24 | } 25 | 26 | return totalSize 27 | } 28 | 29 | // TransferSite : Transfer the site to another user 30 | func TransferSite(site models.Site, lastOwner models.User, newOwner models.User) error { 31 | collections := []string{contactCollection, aboutCollection, galleriesCollection, projectsCollection, photosCollection} 32 | for _, c := range collections { 33 | DB.C(c).Update(bson.M{ 34 | "site_id": site.ID, 35 | }, bson.M{ 36 | "$set": bson.M{ 37 | "owner_id": newOwner.ID, 38 | }, 39 | }) 40 | } 41 | 42 | err := DB.C(sitesCollection).UpdateId(site.ID, bson.M{ 43 | "$set": bson.M{ 44 | "owner_id": newOwner.ID, 45 | }, 46 | }) 47 | 48 | RemoveCollaborator(site.ID, newOwner.Email) 49 | 50 | AddCollaborator(site.ID, models.Collaborator{ 51 | Email: lastOwner.Email, 52 | Role: "collaborator", 53 | }) 54 | 55 | return err 56 | } 57 | 58 | // GetSitesOfUser : return array of Site of specific user 59 | func GetSitesOfUser(user models.User) ([]models.Site, error) { 60 | var sites []models.Site 61 | err := DB.C(sitesCollection).Find(bson.M{ 62 | "$or": []bson.M{ 63 | { 64 | "owner_id": user.ID, 65 | }, 66 | { 67 | "collaborators": models.Collaborator{ 68 | Email: user.Email, 69 | Role: "collaborator", 70 | }, 71 | }, 72 | }, 73 | }).All(&sites) 74 | 75 | /* Dynamicly set favorite */ 76 | for i := range sites { 77 | for _, id := range user.FavoriteSites { 78 | if sites[i].ID == id { 79 | sites[i].Favorite = true 80 | } 81 | } 82 | for _, collaborator := range sites[i].Collaborators { 83 | if collaborator.Email == user.Email { 84 | sites[i].Role = "collaborator" 85 | } 86 | } 87 | if sites[i].Role == "" { 88 | sites[i].Role = "owner" 89 | } 90 | } 91 | 92 | return sites, err 93 | } 94 | 95 | // GetOwnedSites : return only owned sites (and not the ones you might collaborate with) 96 | func GetOwnedSites(id bson.ObjectId) ([]models.Site, error) { 97 | var sites []models.Site 98 | err := DB.C(sitesCollection).Find(bson.M{ 99 | "owner_id": id, 100 | }).All(&sites) 101 | return sites, err 102 | } 103 | 104 | // AddCollaborator : add collaborator to site 105 | func AddCollaborator(id bson.ObjectId, collaborator models.Collaborator) error { 106 | err := DB.C(sitesCollection).UpdateId(id, bson.M{ 107 | "$push": bson.M{ 108 | "collaborators": collaborator, 109 | }, 110 | }) 111 | return err 112 | } 113 | 114 | // RemoveCollaborator : remove collaborator from site 115 | func RemoveCollaborator(id bson.ObjectId, email string) error { 116 | err := DB.C(sitesCollection).UpdateId(id, bson.M{ 117 | "$pull": bson.M{ 118 | "collaborators": bson.M{ 119 | "role": "collaborator", 120 | "email": email, 121 | }}, 122 | }) 123 | return err 124 | } 125 | 126 | // GetSiteByName : return specific site 127 | func GetSiteByName(name string) (models.Site, error) { 128 | var site models.Site 129 | err := DB.C(sitesCollection).Find(bson.M{ 130 | "name": name, 131 | }).One(&site) 132 | return site, err 133 | } 134 | 135 | // GetSiteByID : return specific site by ObjectID 136 | func GetSiteByID(id bson.ObjectId) (models.Site, error) { 137 | var site models.Site 138 | err := DB.C(sitesCollection).FindId(id).One(&site) 139 | return site, err 140 | } 141 | 142 | // AddModule : add module to site 143 | func AddModule(site models.Site, module string) error { 144 | err := DB.C(sitesCollection).UpdateId(site.ID, bson.M{ 145 | "$push": bson.M{ 146 | "modules": constants.Module(module), 147 | }, 148 | }) 149 | return err 150 | } 151 | 152 | // RemoveModule : remove module from site 153 | func RemoveModule(site models.Site, module string) error { 154 | err := DB.C(sitesCollection).UpdateId(site.ID, bson.M{ 155 | "$pull": bson.M{ 156 | "modules": constants.Module(module), 157 | }, 158 | }) 159 | 160 | if err != nil { 161 | return err 162 | } 163 | 164 | //TODO: Remove data from other modules (videos, articles, music...) 165 | // Or ask user to delete it first idk 166 | 167 | if module == "galleries" { 168 | galleries, _ := GetGalleries(site.ID) 169 | 170 | for _, g := range galleries { 171 | photos, _ := GetGalleryPhotos(g.ID) 172 | utils.RemoveGoogleCloudPhotos(photos) 173 | } 174 | _, err := DB.C(photosCollection).RemoveAll(bson.M{ 175 | "site_id": site.ID, 176 | }) 177 | _, err = DB.C(galleriesCollection).RemoveAll(bson.M{ 178 | "site_id": site.ID, 179 | }) 180 | return err 181 | } 182 | 183 | if module == "projects" { 184 | _, err := DB.C(projectsCollection).RemoveAll(bson.M{ 185 | "site_id": site.ID, 186 | }) 187 | return err 188 | } 189 | //TODO remove articles 190 | return err 191 | } 192 | 193 | // UpdateSite : update site data 194 | func UpdateSite(id bson.ObjectId, data models.Site) error { 195 | 196 | err := DB.C(sitesCollection).UpdateId(id, bson.M{ 197 | "$set": bson.M{ 198 | "updated_at": time.Now(), 199 | "display_name": data.DisplayName, 200 | "name": data.Name, 201 | }, 202 | }) 203 | return err 204 | } 205 | 206 | // CreateSite : insert site in db 207 | func CreateSite(site models.Site) (models.Site, error) { 208 | site.CreatedAt = time.Now() 209 | site.UpdatedAt = time.Now() 210 | site.ID = bson.NewObjectId() 211 | 212 | err := DB.C(sitesCollection).Insert(site) 213 | 214 | UpdateContact(site.ID, models.ContactContent{OwnerID: site.OwnerID, SiteID: site.ID}) 215 | UpdateAbout(site.ID, models.AboutContent{OwnerID: site.OwnerID, SiteID: site.ID}) 216 | 217 | return site, err 218 | } 219 | 220 | // DeleteSite : complete erase 221 | func DeleteSite(site models.Site) error { 222 | collections := []string{contactCollection, aboutCollection, galleriesCollection, projectsCollection, photosCollection} 223 | for _, c := range collections { 224 | DB.C(c).RemoveAll(bson.M{ 225 | "site_id": site.ID, 226 | }) 227 | } 228 | return DB.C(sitesCollection).RemoveId(site.ID) 229 | } 230 | 231 | // SiteExists : check if site exists 232 | func SiteExists(name string) bool { 233 | count, _ := DB.C(sitesCollection).Find(bson.M{ 234 | "name": name, 235 | }).Count() 236 | return count > 0 237 | } 238 | 239 | // SetSiteFavorite : set site favorite 240 | func SetSiteFavorite(user models.User, siteID bson.ObjectId) error { 241 | isFavorite := false 242 | for _, sID := range user.FavoriteSites { 243 | if siteID == sID { 244 | isFavorite = true 245 | } 246 | } 247 | if isFavorite { 248 | return DB.C(usersCollection).UpdateId(user.ID, bson.M{ 249 | "$pull": bson.M{ 250 | "favorite_sites": siteID, 251 | }, 252 | }) 253 | } 254 | return DB.C(usersCollection).UpdateId(user.ID, bson.M{ 255 | "$push": bson.M{ 256 | "favorite_sites": siteID, 257 | }, 258 | }) 259 | } 260 | -------------------------------------------------------------------------------- /handlers/admin/galleries.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/backpulse/core/database" 10 | "github.com/backpulse/core/models" 11 | "github.com/backpulse/core/utils" 12 | "github.com/gorilla/mux" 13 | "github.com/teris-io/shortid" 14 | "gopkg.in/mgo.v2/bson" 15 | ) 16 | 17 | // DeleteGallery handler 18 | func DeleteGallery(w http.ResponseWriter, r *http.Request) { 19 | vars := mux.Vars(r) 20 | id := vars["id"] 21 | 22 | gallery, err := database.GetGallery(bson.ObjectIdHex(id)) 23 | if err != nil { 24 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 25 | return 26 | } 27 | 28 | site, _ := database.GetSiteByID(gallery.SiteID) 29 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 30 | 31 | if !utils.IsAuthorized(site, user) { 32 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 33 | return 34 | } 35 | 36 | err = database.DeleteGallery(gallery.ID) 37 | if err != nil { 38 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 39 | } 40 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 41 | return 42 | } 43 | 44 | // GetGallery return gallery 45 | func GetGallery(w http.ResponseWriter, r *http.Request) { 46 | vars := mux.Vars(r) 47 | id := vars["id"] 48 | 49 | gallery, err := database.GetGallery(bson.ObjectIdHex(id)) 50 | if err != nil { 51 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 52 | return 53 | } 54 | site, _ := database.GetSiteByID(gallery.SiteID) 55 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 56 | 57 | if !utils.IsAuthorized(site, user) { 58 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 59 | return 60 | } 61 | 62 | utils.RespondWithJSON(w, http.StatusOK, "success", gallery) 63 | return 64 | } 65 | 66 | // GetGalleries : return array of gallery 67 | func GetGalleries(w http.ResponseWriter, r *http.Request) { 68 | vars := mux.Vars(r) 69 | name := vars["name"] 70 | 71 | site, _ := database.GetSiteByName(name) 72 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 73 | 74 | if !utils.IsAuthorized(site, user) { 75 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 76 | return 77 | } 78 | 79 | galleries, err := database.GetGalleries(site.ID) 80 | if err != nil { 81 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 82 | return 83 | } 84 | 85 | utils.RespondWithJSON(w, http.StatusOK, "success", galleries) 86 | return 87 | 88 | } 89 | 90 | func SetGalleryPreview(w http.ResponseWriter, r *http.Request) { 91 | vars := mux.Vars(r) 92 | siteName := vars["name"] 93 | galleryID := vars["galleryID"] 94 | photoID := vars["id"] 95 | 96 | site, _ := database.GetSiteByName(siteName) 97 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 98 | 99 | if !utils.IsAuthorized(site, user) { 100 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 101 | return 102 | } 103 | 104 | gallery, err := database.GetGallery(bson.ObjectIdHex(galleryID)) 105 | if err != nil { 106 | log.Print(err) 107 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 108 | return 109 | } 110 | 111 | if gallery.SiteID != site.ID { 112 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 113 | return 114 | } 115 | 116 | if !bson.IsObjectIdHex(photoID) { 117 | log.Print(err) 118 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 119 | return 120 | } 121 | 122 | photo, err := database.GetPhotoByID(bson.ObjectIdHex(photoID)) 123 | if err != nil { 124 | log.Print(err) 125 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 126 | return 127 | } 128 | if photo.SiteID != site.ID || photo.GalleryID != &gallery.ID { 129 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 130 | return 131 | } 132 | 133 | err = database.SetGalleryPreview(gallery, photo) 134 | if err != nil { 135 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 136 | return 137 | } 138 | 139 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 140 | return 141 | } 142 | 143 | func SetDefaultGallery(w http.ResponseWriter, r *http.Request) { 144 | vars := mux.Vars(r) 145 | siteName := vars["name"] 146 | galleryID := vars["id"] 147 | 148 | site, _ := database.GetSiteByName(siteName) 149 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 150 | 151 | if !utils.IsAuthorized(site, user) { 152 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 153 | return 154 | } 155 | 156 | gallery, err := database.GetGallery(bson.ObjectIdHex(galleryID)) 157 | if err != nil { 158 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 159 | return 160 | } 161 | 162 | if gallery.SiteID != site.ID { 163 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 164 | return 165 | } 166 | 167 | err = database.SetDefaultGallery(site, gallery) 168 | if err != nil { 169 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 170 | return 171 | } 172 | 173 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 174 | return 175 | } 176 | 177 | // UpdateGallery handler 178 | func UpdateGallery(w http.ResponseWriter, r *http.Request) { 179 | vars := mux.Vars(r) 180 | id := vars["id"] 181 | 182 | gallery, err := database.GetGallery(bson.ObjectIdHex(id)) 183 | if err != nil { 184 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 185 | return 186 | } 187 | site, _ := database.GetSiteByID(gallery.SiteID) 188 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 189 | 190 | if !utils.IsAuthorized(site, user) { 191 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 192 | return 193 | } 194 | 195 | var galleryData models.Gallery 196 | /* Parse json to models.Gallery */ 197 | err = json.NewDecoder(r.Body).Decode(&galleryData) 198 | if err != nil { 199 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 200 | return 201 | } 202 | 203 | err = database.UpdateGallery(gallery.ID, galleryData) 204 | if err != nil { 205 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 206 | return 207 | } 208 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 209 | return 210 | } 211 | 212 | // CreateGallery handler 213 | func CreateGallery(w http.ResponseWriter, r *http.Request) { 214 | vars := mux.Vars(r) 215 | siteName := vars["name"] 216 | galleryName := vars["galleryName"] 217 | 218 | site, _ := database.GetSiteByName(siteName) 219 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 220 | 221 | if !utils.IsAuthorized(site, user) { 222 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 223 | return 224 | } 225 | 226 | if len(galleryName) > 150 { 227 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_too_long", nil) 228 | return 229 | } 230 | 231 | if len(galleryName) < 1 { 232 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_required", nil) 233 | return 234 | } 235 | 236 | galleries, _ := database.GetGalleries(site.ID) 237 | 238 | shortID, _ := shortid.Generate() 239 | gallery := models.Gallery{ 240 | ID: bson.NewObjectId(), 241 | OwnerID: site.OwnerID, 242 | SiteID: site.ID, 243 | CreatedAt: time.Now(), 244 | UpdatedAt: time.Now(), 245 | ShortID: shortID, 246 | Index: len(galleries), 247 | Titles: []models.Translation{ 248 | { 249 | Content: galleryName, 250 | LanguageCode: "en", 251 | LanguageName: "English", 252 | }, 253 | }, 254 | } 255 | 256 | err := database.CreateGallery(gallery) 257 | if err != nil { 258 | log.Println(err) 259 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 260 | return 261 | } 262 | utils.RespondWithJSON(w, http.StatusOK, "success", gallery) 263 | return 264 | } 265 | 266 | func UpdateGalleriesIndexes(w http.ResponseWriter, r *http.Request) { 267 | vars := mux.Vars(r) 268 | siteName := vars["name"] 269 | 270 | site, _ := database.GetSiteByName(siteName) 271 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 272 | 273 | if !utils.IsAuthorized(site, user) { 274 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 275 | return 276 | } 277 | 278 | var galleries []models.Gallery 279 | /* Parse json to models.Gallery */ 280 | err := json.NewDecoder(r.Body).Decode(&galleries) 281 | if err != nil { 282 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 283 | return 284 | } 285 | 286 | err = database.UpdateGalleriesIndexes(site.ID, galleries) 287 | if err != nil { 288 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 289 | return 290 | } 291 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 292 | return 293 | } 294 | -------------------------------------------------------------------------------- /constants/languages.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | type Language struct { 4 | Name string 5 | Code string 6 | } 7 | 8 | var Languages []Language = []Language{ 9 | { 10 | Name: "Afar", 11 | Code: "aa", 12 | }, { 13 | Name: "Abkhazian", 14 | Code: "ab", 15 | }, { 16 | Name: "Avestan", 17 | Code: "ae", 18 | }, { 19 | Name: "Afrikaans", 20 | Code: "af", 21 | }, { 22 | Name: "Akan", 23 | Code: "ak", 24 | }, { 25 | Name: "Amharic", 26 | Code: "am", 27 | }, { 28 | Name: "Aragonese", 29 | Code: "an", 30 | }, { 31 | Name: "Arabic", 32 | Code: "ar", 33 | }, { 34 | Name: "Assamese", 35 | Code: "as", 36 | }, { 37 | Name: "Avaric", 38 | Code: "av", 39 | }, { 40 | Name: "Aymara", 41 | Code: "ay", 42 | }, { 43 | Name: "Azerbaijani", 44 | Code: "az", 45 | }, { 46 | Name: "Bashkir", 47 | Code: "ba", 48 | }, { 49 | Name: "Belarusian", 50 | Code: "be", 51 | }, { 52 | Name: "Bulgarian", 53 | Code: "bg", 54 | }, { 55 | Name: "Bihari", 56 | Code: "bh", 57 | }, { 58 | Name: "Bislama", 59 | Code: "bi", 60 | }, { 61 | Name: "Bambara", 62 | Code: "bm", 63 | }, { 64 | Name: "Bengali", 65 | Code: "bn", 66 | }, { 67 | Name: "Tibetan", 68 | Code: "bo", 69 | }, { 70 | Name: "Breton", 71 | Code: "br", 72 | }, { 73 | Name: "Bosnian", 74 | Code: "bs", 75 | }, { 76 | Name: "Catalan", 77 | Code: "ca", 78 | }, { 79 | Name: "Chechen", 80 | Code: "ce", 81 | }, { 82 | Name: "Chamorro", 83 | Code: "ch", 84 | }, { 85 | Name: "Corsican", 86 | Code: "co", 87 | }, { 88 | Name: "Cree", 89 | Code: "cr", 90 | }, { 91 | Name: "Czech", 92 | Code: "cs", 93 | }, { 94 | Name: "Church Slavic", 95 | Code: "cu", 96 | }, { 97 | Name: "Chuvash", 98 | Code: "cv", 99 | }, { 100 | Name: "Welsh", 101 | Code: "cy", 102 | }, { 103 | Name: "Danish", 104 | Code: "da", 105 | }, { 106 | Name: "German", 107 | Code: "de", 108 | }, { 109 | Name: "Divehi", 110 | Code: "dv", 111 | }, { 112 | Name: "Dzongkha", 113 | Code: "dz", 114 | }, { 115 | Name: "Ewe", 116 | Code: "ee", 117 | }, { 118 | Name: "Greek", 119 | Code: "el", 120 | }, { 121 | Name: "English", 122 | Code: "en", 123 | }, { 124 | Name: "Esperanto", 125 | Code: "eo", 126 | }, { 127 | Name: "Spanish", 128 | Code: "es", 129 | }, { 130 | Name: "Estonian", 131 | Code: "et", 132 | }, { 133 | Name: "Basque", 134 | Code: "eu", 135 | }, { 136 | Name: "Persian", 137 | Code: "fa", 138 | }, { 139 | Name: "Fulah", 140 | Code: "ff", 141 | }, { 142 | Name: "Finnish", 143 | Code: "fi", 144 | }, { 145 | Name: "Fijian", 146 | Code: "fj", 147 | }, { 148 | Name: "Faroese", 149 | Code: "fo", 150 | }, { 151 | Name: "French", 152 | Code: "fr", 153 | }, { 154 | Name: "Western Frisian", 155 | Code: "fy", 156 | }, { 157 | Name: "Irish", 158 | Code: "ga", 159 | }, { 160 | Name: "Gaelic", 161 | Code: "gd", 162 | }, { 163 | Name: "Galician", 164 | Code: "gl", 165 | }, { 166 | Name: "Guarani", 167 | Code: "gn", 168 | }, { 169 | Name: "Gujarati", 170 | Code: "gu", 171 | }, { 172 | Name: "Manx", 173 | Code: "gv", 174 | }, { 175 | Name: "Hausa", 176 | Code: "ha", 177 | }, { 178 | Name: "Hebrew", 179 | Code: "he", 180 | }, { 181 | Name: "Hindi", 182 | Code: "hi", 183 | }, { 184 | Name: "Hiri Motu", 185 | Code: "ho", 186 | }, { 187 | Name: "Croatian", 188 | Code: "hr", 189 | }, { 190 | Name: "Haitian", 191 | Code: "ht", 192 | }, { 193 | Name: "Hungarian", 194 | Code: "hu", 195 | }, { 196 | Name: "Armenian", 197 | Code: "hy", 198 | }, { 199 | Name: "Herero", 200 | Code: "hz", 201 | }, { 202 | Name: "Interlingua", 203 | Code: "ia", 204 | }, { 205 | Name: "Indonesian", 206 | Code: "id", 207 | }, { 208 | Name: "Interlingue", 209 | Code: "ie", 210 | }, { 211 | Name: "Igbo", 212 | Code: "ig", 213 | }, { 214 | Name: "Sichuan Yi", 215 | Code: "ii", 216 | }, { 217 | Name: "Inupiaq", 218 | Code: "ik", 219 | }, { 220 | Name: "Ido", 221 | Code: "io", 222 | }, { 223 | Name: "Icelandic", 224 | Code: "is", 225 | }, { 226 | Name: "Italian", 227 | Code: "it", 228 | }, { 229 | Name: "Inuktitut", 230 | Code: "iu", 231 | }, { 232 | Name: "Japanese", 233 | Code: "ja", 234 | }, { 235 | Name: "Javanese", 236 | Code: "jv", 237 | }, { 238 | Name: "Georgian", 239 | Code: "ka", 240 | }, { 241 | Name: "Kongo", 242 | Code: "kg", 243 | }, { 244 | Name: "Kikuyu", 245 | Code: "ki", 246 | }, { 247 | Name: "Kuanyama", 248 | Code: "kj", 249 | }, { 250 | Name: "Kazakh", 251 | Code: "kk", 252 | }, { 253 | Name: "Kalaallisut", 254 | Code: "kl", 255 | }, { 256 | Name: "Central Khmer", 257 | Code: "km", 258 | }, { 259 | Name: "Kannada", 260 | Code: "kn", 261 | }, { 262 | Name: "Korean", 263 | Code: "ko", 264 | }, { 265 | Name: "Kanuri", 266 | Code: "kr", 267 | }, { 268 | Name: "Kashmiri", 269 | Code: "ks", 270 | }, { 271 | Name: "Kurdish", 272 | Code: "ku", 273 | }, { 274 | Name: "Komi", 275 | Code: "kv", 276 | }, { 277 | Name: "Cornish", 278 | Code: "kw", 279 | }, { 280 | Name: "Kirghiz", 281 | Code: "ky", 282 | }, { 283 | Name: "Latin", 284 | Code: "la", 285 | }, { 286 | Name: "Luxembourgish", 287 | Code: "lb", 288 | }, { 289 | Name: "Ganda", 290 | Code: "lg", 291 | }, { 292 | Name: "Limburgan", 293 | Code: "li", 294 | }, { 295 | Name: "Lingala", 296 | Code: "ln", 297 | }, { 298 | Name: "Lao", 299 | Code: "lo", 300 | }, { 301 | Name: "Lithuanian", 302 | Code: "lt", 303 | }, { 304 | Name: "Luba-Katanga", 305 | Code: "lu", 306 | }, { 307 | Name: "Latvian", 308 | Code: "lv", 309 | }, { 310 | Name: "Malagasy", 311 | Code: "mg", 312 | }, { 313 | Name: "Marshallese", 314 | Code: "mh", 315 | }, { 316 | Name: "Maori", 317 | Code: "mi", 318 | }, { 319 | Name: "Macedonian", 320 | Code: "mk", 321 | }, { 322 | Name: "Malayalam", 323 | Code: "ml", 324 | }, { 325 | Name: "Mongolian", 326 | Code: "mn", 327 | }, { 328 | Name: "Marathi", 329 | Code: "mr", 330 | }, { 331 | Name: "Malay", 332 | Code: "ms", 333 | }, { 334 | Name: "Maltese", 335 | Code: "mt", 336 | }, { 337 | Name: "Burmese", 338 | Code: "my", 339 | }, { 340 | Name: "Nauru", 341 | Code: "na", 342 | }, { 343 | Name: "Bokmål", 344 | Code: "nb", 345 | }, { 346 | Name: "Ndebele", 347 | Code: "nd", 348 | }, { 349 | Name: "Nepali", 350 | Code: "ne", 351 | }, { 352 | Name: "Ndonga", 353 | Code: "ng", 354 | }, { 355 | Name: "Dutch", 356 | Code: "nl", 357 | }, { 358 | Name: "Norwegian Nynorsk", 359 | Code: "nn", 360 | }, { 361 | Name: "Norwegian", 362 | Code: "no", 363 | }, { 364 | Name: "Ndebele", 365 | Code: "nr", 366 | }, { 367 | Name: "Navajo", 368 | Code: "nv", 369 | }, { 370 | Name: "Chichewa", 371 | Code: "ny", 372 | }, { 373 | Name: "Occitan", 374 | Code: "oc", 375 | }, { 376 | Name: "Ojibwa", 377 | Code: "oj", 378 | }, { 379 | Name: "Oromo", 380 | Code: "om", 381 | }, { 382 | Name: "Oriya", 383 | Code: "or", 384 | }, { 385 | Name: "Ossetian", 386 | Code: "os", 387 | }, { 388 | Name: "Panjabi", 389 | Code: "pa", 390 | }, { 391 | Name: "Pali", 392 | Code: "pi", 393 | }, { 394 | Name: "Polish", 395 | Code: "pl", 396 | }, { 397 | Name: "Pushto", 398 | Code: "ps", 399 | }, { 400 | Name: "Portuguese", 401 | Code: "pt", 402 | }, { 403 | Name: "Quechua", 404 | Code: "qu", 405 | }, { 406 | Name: "Romansh", 407 | Code: "rm", 408 | }, { 409 | Name: "Rundi", 410 | Code: "rn", 411 | }, { 412 | Name: "Romanian", 413 | Code: "ro", 414 | }, { 415 | Name: "Russian", 416 | Code: "ru", 417 | }, { 418 | Name: "Kinyarwanda", 419 | Code: "rw", 420 | }, { 421 | Name: "Sanskrit", 422 | Code: "sa", 423 | }, { 424 | Name: "Sardinian", 425 | Code: "sc", 426 | }, { 427 | Name: "Sindhi", 428 | Code: "sd", 429 | }, { 430 | Name: "Northern Sami", 431 | Code: "se", 432 | }, { 433 | Name: "Sango", 434 | Code: "sg", 435 | }, { 436 | Name: "Sinhala", 437 | Code: "si", 438 | }, { 439 | Name: "Slovak", 440 | Code: "sk", 441 | }, { 442 | Name: "Slovenian", 443 | Code: "sl", 444 | }, { 445 | Name: "Samoan", 446 | Code: "sm", 447 | }, { 448 | Name: "Shona", 449 | Code: "sn", 450 | }, { 451 | Name: "Somali", 452 | Code: "so", 453 | }, { 454 | Name: "Albanian", 455 | Code: "sq", 456 | }, { 457 | Name: "Serbian", 458 | Code: "sr", 459 | }, { 460 | Name: "Swati", 461 | Code: "ss", 462 | }, { 463 | Name: "Sotho", 464 | Code: "st", 465 | }, { 466 | Name: "Sundanese", 467 | Code: "su", 468 | }, { 469 | Name: "Swedish", 470 | Code: "sv", 471 | }, { 472 | Name: "Swahili", 473 | Code: "sw", 474 | }, { 475 | Name: "Tamil", 476 | Code: "ta", 477 | }, { 478 | Name: "Telugu", 479 | Code: "te", 480 | }, { 481 | Name: "Tajik", 482 | Code: "tg", 483 | }, { 484 | Name: "Thai", 485 | Code: "th", 486 | }, { 487 | Name: "Tigrinya", 488 | Code: "ti", 489 | }, { 490 | Name: "Turkmen", 491 | Code: "tk", 492 | }, { 493 | Name: "Tagalog", 494 | Code: "tl", 495 | }, { 496 | Name: "Tswana", 497 | Code: "tn", 498 | }, { 499 | Name: "Tonga", 500 | Code: "to", 501 | }, { 502 | Name: "Turkish", 503 | Code: "tr", 504 | }, { 505 | Name: "Tsonga", 506 | Code: "ts", 507 | }, { 508 | Name: "Tatar", 509 | Code: "tt", 510 | }, { 511 | Name: "Twi", 512 | Code: "tw", 513 | }, { 514 | Name: "Tahitian", 515 | Code: "ty", 516 | }, { 517 | Name: "Uighur", 518 | Code: "ug", 519 | }, { 520 | Name: "Ukrainian", 521 | Code: "uk", 522 | }, { 523 | Name: "Urdu", 524 | Code: "ur", 525 | }, { 526 | Name: "Uzbek", 527 | Code: "uz", 528 | }, { 529 | Name: "Venda", 530 | Code: "ve", 531 | }, { 532 | Name: "Vietnamese", 533 | Code: "vi", 534 | }, { 535 | Name: "Volapük", 536 | Code: "vo", 537 | }, { 538 | Name: "Walloon", 539 | Code: "wa", 540 | }, { 541 | Name: "Wolof", 542 | Code: "wo", 543 | }, { 544 | Name: "Xhosa", 545 | Code: "xh", 546 | }, { 547 | Name: "Yiddish", 548 | Code: "yi", 549 | }, { 550 | Name: "Yoruba", 551 | Code: "yo", 552 | }, { 553 | Name: "Zhuang", 554 | Code: "za", 555 | }, { 556 | Name: "Chinese", 557 | Code: "zh", 558 | }, { 559 | Name: "Zulu", 560 | Code: "zu", 561 | }, 562 | } 563 | -------------------------------------------------------------------------------- /handlers/admin/photos.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "math" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/backpulse/core/database" 12 | "github.com/backpulse/core/models" 13 | "github.com/backpulse/core/utils" 14 | "github.com/gorilla/mux" 15 | "gopkg.in/mgo.v2/bson" 16 | ) 17 | 18 | // UpdatePhotosIndexes : change order of photos 19 | func UpdatePhotosIndexes(w http.ResponseWriter, r *http.Request) { 20 | vars := mux.Vars(r) 21 | siteName := vars["name"] 22 | galleryID := vars["id"] 23 | 24 | site, _ := database.GetSiteByName(siteName) 25 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 26 | 27 | if !utils.IsAuthorized(site, user) { 28 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 29 | return 30 | } 31 | 32 | gallery, err := database.GetGallery(bson.ObjectIdHex(galleryID)) 33 | if err != nil { 34 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 35 | return 36 | } 37 | 38 | if gallery.SiteID != site.ID { 39 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 40 | return 41 | } 42 | 43 | var photos []models.Photo 44 | /* Parse json to models.Gallery */ 45 | err = json.NewDecoder(r.Body).Decode(&photos) 46 | if err != nil { 47 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 48 | return 49 | } 50 | 51 | err = database.UpdatePhotosIndexes(gallery, photos) 52 | if err != nil { 53 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 54 | return 55 | } 56 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 57 | return 58 | } 59 | 60 | // DeletePhotos : remove photos from db & g cloud 61 | func DeletePhotos(w http.ResponseWriter, r *http.Request) { 62 | vars := mux.Vars(r) 63 | ids := vars["ids"] 64 | stringIDsArray := strings.Split(ids, ",") 65 | var array []bson.ObjectId 66 | log.Println(stringIDsArray) 67 | for _, id := range stringIDsArray { 68 | if bson.IsObjectIdHex(id) { 69 | array = append(array, bson.ObjectIdHex(id)) 70 | } 71 | } 72 | 73 | log.Println(array) 74 | 75 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 76 | 77 | photos, err := database.GetPhotos(user.ID, array) 78 | if err != nil { 79 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 80 | return 81 | } 82 | 83 | var site models.Site 84 | for _, photo := range photos { 85 | if photo.SiteID != site.ID { 86 | site, _ = database.GetSiteByID(photo.SiteID) 87 | } 88 | if !utils.IsAuthorized(site, user) { 89 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 90 | return 91 | } 92 | } 93 | 94 | err = database.DeletePhotos(user.ID, array) 95 | if err != nil { 96 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 97 | return 98 | } 99 | err = utils.RemoveGoogleCloudPhotos(photos) 100 | // if err != nil { 101 | // utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 102 | // return 103 | // } 104 | 105 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 106 | return 107 | } 108 | 109 | // GetPhotos : return photos 110 | func GetPhotos(w http.ResponseWriter, r *http.Request) { 111 | vars := mux.Vars(r) 112 | siteName := vars["name"] 113 | 114 | site, _ := database.GetSiteByName(siteName) 115 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 116 | 117 | if !utils.IsAuthorized(site, user) { 118 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 119 | return 120 | } 121 | 122 | photos, err := database.GetSitePhotos(site.ID) 123 | if err != nil { 124 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 125 | return 126 | } 127 | utils.RespondWithJSON(w, http.StatusOK, "success", photos) 128 | return 129 | } 130 | 131 | func UpdatePhotoFile(w http.ResponseWriter, r *http.Request) { 132 | vars := mux.Vars(r) 133 | photoID := bson.ObjectIdHex(vars["id"]) 134 | name := vars["name"] 135 | /* Get file from Client */ 136 | image, header, err := r.FormFile("image") 137 | if err != nil { 138 | log.Println(err) 139 | utils.RespondWithJSON(w, http.StatusInternalServerError, err.Error(), nil) 140 | return 141 | } 142 | defer image.Close() 143 | 144 | if !strings.HasPrefix(header.Header.Get("Content-Type"), "image") { 145 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "not_an_image", nil) 146 | return 147 | } 148 | site, _ := database.GetSiteByName(name) 149 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 150 | 151 | if !utils.IsAuthorized(site, user) { 152 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 153 | return 154 | } 155 | 156 | photo := models.Photo{ 157 | Size: math.Round(float64(header.Size)/10000) / 100, 158 | Format: header.Header.Get("Content-Type"), 159 | } 160 | 161 | id, err := utils.UploadFile(image, header.Filename) 162 | if err != nil { 163 | log.Println(err) 164 | utils.RespondWithJSON(w, http.StatusInternalServerError, err.Error(), nil) 165 | return 166 | } 167 | config := utils.GetConfig() 168 | url := config.BucketPubURL + "/" + id.Hex() 169 | 170 | err = database.UpdatePhotoURL(photoID, url) 171 | if err != nil { 172 | utils.RespondWithJSON(w, http.StatusInternalServerError, err.Error(), nil) 173 | return 174 | } 175 | 176 | utils.RespondWithJSON(w, http.StatusOK, "success", photo) 177 | return 178 | 179 | } 180 | 181 | // UploadPhoto handler 182 | func UploadPhoto(w http.ResponseWriter, r *http.Request) { 183 | /* Get file from Client */ 184 | image, header, err := r.FormFile("image") 185 | if err != nil { 186 | utils.RespondWithJSON(w, http.StatusInternalServerError, err.Error(), nil) 187 | return 188 | } 189 | defer image.Close() 190 | 191 | vars := mux.Vars(r) 192 | name := vars["name"] 193 | 194 | if !strings.HasPrefix(header.Header.Get("Content-Type"), "image") { 195 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "not_an_image", nil) 196 | return 197 | } 198 | site, _ := database.GetSiteByName(name) 199 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 200 | 201 | if !utils.IsAuthorized(site, user) { 202 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 203 | return 204 | } 205 | 206 | photo := models.Photo{ 207 | Size: math.Round(float64(header.Size)/10000) / 100, 208 | CreatedAt: time.Now(), 209 | OwnerID: site.OwnerID, 210 | Format: header.Header.Get("Content-Type"), 211 | SiteID: site.ID, 212 | } 213 | 214 | if r.FormValue("is_gallery") == "true" { 215 | // This is a gallery photos 216 | galleryID := r.FormValue("gallery_id") 217 | photo.IsGallery = true 218 | g, err := database.GetGallery(bson.ObjectIdHex(galleryID)) 219 | if err != nil { 220 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 221 | return 222 | } 223 | if g.OwnerID != site.OwnerID { 224 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 225 | return 226 | } 227 | photo.GalleryID = &g.ID 228 | photo.Title = r.FormValue("title") 229 | photo.Content = r.FormValue("content") 230 | 231 | } else if r.FormValue("is_project") == "true" { 232 | // This is a project photo 233 | photo.IsProject = true 234 | projectID := r.FormValue("project_id") 235 | 236 | p, err := database.GetProject(bson.ObjectIdHex(projectID)) 237 | if err != nil { 238 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 239 | return 240 | } 241 | if p.OwnerID != site.OwnerID { 242 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 243 | return 244 | } 245 | photo.ProjectID = p.ID 246 | } else { 247 | utils.RespondWithJSON(w, http.StatusBadRequest, "not_acceptable", nil) 248 | return 249 | } 250 | 251 | id, err := utils.UploadFile(image, header.Filename) 252 | if err != nil { 253 | utils.RespondWithJSON(w, http.StatusInternalServerError, err.Error(), nil) 254 | return 255 | } 256 | photo.ID = id 257 | config := utils.GetConfig() 258 | photo.URL = config.BucketPubURL + "/" + id.Hex() 259 | 260 | photo, err = database.InsertPhoto(photo) 261 | if err != nil { 262 | log.Println(err) 263 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 264 | return 265 | } 266 | utils.RespondWithJSON(w, http.StatusOK, "success", photo) 267 | return 268 | } 269 | 270 | func CreatePhoto(w http.ResponseWriter, r *http.Request) { 271 | vars := mux.Vars(r) 272 | siteName := vars["name"] 273 | 274 | site, _ := database.GetSiteByName(siteName) 275 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 276 | 277 | if !utils.IsAuthorized(site, user) { 278 | log.Println("unauthorized") 279 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 280 | return 281 | } 282 | 283 | var photo models.Photo 284 | /* Parse json to models.Project */ 285 | err := json.NewDecoder(r.Body).Decode(&photo) 286 | if err != nil { 287 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 288 | return 289 | } 290 | 291 | photo.OwnerID = site.OwnerID 292 | photo.SiteID = site.ID 293 | 294 | log.Println(photo) 295 | 296 | photo, err = database.CreatePhoto(photo) 297 | if err != nil { 298 | log.Println(err) 299 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 300 | return 301 | } 302 | utils.RespondWithJSON(w, http.StatusOK, "success", photo) 303 | return 304 | 305 | } 306 | 307 | func GetPhoto(w http.ResponseWriter, r *http.Request) { 308 | vars := mux.Vars(r) 309 | siteName := vars["name"] 310 | photoID := vars["id"] 311 | 312 | site, _ := database.GetSiteByName(siteName) 313 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 314 | 315 | if !utils.IsAuthorized(site, user) { 316 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 317 | return 318 | } 319 | 320 | photo, err := database.GetPhotoByID(bson.ObjectIdHex(photoID)) 321 | if err != nil { 322 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 323 | return 324 | } 325 | 326 | if photo.GalleryID != nil { 327 | gallery, _ := database.GetGallery(*photo.GalleryID) 328 | photo.GalleryName = gallery.Title 329 | } 330 | 331 | utils.RespondWithJSON(w, http.StatusOK, "success", photo) 332 | return 333 | } 334 | 335 | func UpdatePhoto(w http.ResponseWriter, r *http.Request) { 336 | vars := mux.Vars(r) 337 | siteName := vars["name"] 338 | photoID := vars["id"] 339 | 340 | site, _ := database.GetSiteByName(siteName) 341 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 342 | 343 | if !utils.IsAuthorized(site, user) { 344 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 345 | return 346 | } 347 | 348 | var photo models.Photo 349 | /* Parse json to models.Project */ 350 | err := json.NewDecoder(r.Body).Decode(&photo) 351 | if err != nil { 352 | log.Println(err) 353 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 354 | return 355 | } 356 | 357 | err = database.UpdatePhoto(bson.ObjectIdHex(photoID), photo) 358 | if err != nil { 359 | log.Println(err) 360 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 361 | return 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /handlers/admin/users.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "github.com/backpulse/core/database" 11 | "github.com/backpulse/core/models" 12 | "github.com/backpulse/core/utils" 13 | "github.com/gorilla/mux" 14 | "github.com/stripe/stripe-go" 15 | "golang.org/x/crypto/bcrypt" 16 | "gopkg.in/mgo.v2/bson" 17 | ) 18 | 19 | // CreateUser : Handler for `POST /users` 20 | func CreateUser(w http.ResponseWriter, r *http.Request) { 21 | /* Parse JSON into models.User */ 22 | 23 | var user models.User 24 | err := json.NewDecoder(r.Body).Decode(&user) 25 | if err != nil { 26 | utils.RespondWithJSON(w, http.StatusBadRequest, "error", nil) 27 | return 28 | } 29 | 30 | ok, errStr := database.VerifyEmail(user.Email, r) 31 | if !ok { 32 | utils.RespondWithJSON(w, http.StatusNotAcceptable, errStr, nil) 33 | return 34 | } 35 | 36 | /* Check password length */ 37 | if len(user.Password) < 8 { 38 | utils.RespondWithJSON(w, http.StatusUnprocessableEntity, "password_too_short", nil) 39 | return 40 | } 41 | if len(user.Password) > 128 { 42 | utils.RespondWithJSON(w, http.StatusUnprocessableEntity, "password_too_long", nil) 43 | return 44 | } 45 | 46 | /* Hash password */ 47 | password, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 10) 48 | user.Password = string(password) 49 | 50 | /* Insert user in Database */ 51 | user, err = database.AddUser(user) 52 | if err != nil { 53 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 54 | return 55 | } 56 | 57 | tokenString, err := utils.NewJWT(user, 48) 58 | if err != nil { 59 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 60 | return 61 | } 62 | 63 | /* Send token in payload */ 64 | utils.RespondWithJSON(w, http.StatusOK, "success", tokenString) 65 | 66 | verification, _ := database.CreateVerification(user) 67 | err = utils.SendVerificationMail(user.Email, verification) 68 | if err != nil { 69 | log.Println(err) 70 | } 71 | return 72 | } 73 | 74 | // AuthenticateUser authenticates user 75 | func AuthenticateUser(w http.ResponseWriter, r *http.Request) { 76 | /* Load config (secret) */ 77 | 78 | var data models.User 79 | /* Parse json to models.User */ 80 | _ = json.NewDecoder(r.Body).Decode(&data) 81 | 82 | user, err := database.GetUser(data.Email) 83 | if err != nil { 84 | /* User wasn't found */ 85 | utils.RespondWithJSON(w, http.StatusUnauthorized, "auth_fail", nil) 86 | return 87 | } 88 | err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(data.Password)) 89 | if err != nil { 90 | /* Password is not correct */ 91 | utils.RespondWithJSON(w, http.StatusUnauthorized, "auth_fail", nil) 92 | return 93 | } 94 | 95 | /* Congratulation! You made it! Let's give a you a JWT */ 96 | tokenString, err := utils.NewJWT(user, 48) 97 | if err != nil { 98 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 99 | return 100 | } 101 | 102 | /* Send token in payload */ 103 | utils.RespondWithJSON(w, http.StatusOK, "success", tokenString) 104 | return 105 | } 106 | 107 | // GetSelfUser return self data 108 | func GetSelfUser(w http.ResponseWriter, r *http.Request) { 109 | id := utils.GetUserObjectID(r) 110 | user, err := database.GetUserByID(id) 111 | 112 | log.Println(err) 113 | if err != nil { 114 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 115 | return 116 | } 117 | 118 | user.Password = "" 119 | utils.RespondWithJSON(w, http.StatusOK, "success", user) 120 | return 121 | } 122 | 123 | func RemoveSubscription(w http.ResponseWriter, r *http.Request) { 124 | id := utils.GetUserObjectID(r) 125 | user, err := database.GetUserByID(id) 126 | 127 | _, err = utils.StripeClient.Subscriptions.Cancel(user.ActiveSubscriptionID, nil) 128 | if err != nil { 129 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 130 | return 131 | } 132 | 133 | err = database.RemoveUserSubscription(user) 134 | if err != nil { 135 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 136 | return 137 | } 138 | 139 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 140 | return 141 | } 142 | 143 | func ChargeAccount(w http.ResponseWriter, r *http.Request) { 144 | 145 | type chargeRequest struct { 146 | Name string `json:"name"` 147 | Address string `json:"address"` 148 | State string `json:"state"` 149 | ZIP string `json:"zip"` 150 | City string `json:"city"` 151 | Country string `json:"country"` 152 | Token stripe.Token `json:"token"` 153 | } 154 | var data chargeRequest 155 | 156 | id := utils.GetUserObjectID(r) 157 | 158 | user, err := database.GetUserByID(id) 159 | if err != nil { 160 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 161 | return 162 | } 163 | 164 | /* Parse json */ 165 | _ = json.NewDecoder(r.Body).Decode(&data) 166 | 167 | if data.Address == "" { 168 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "missing_address", nil) 169 | return 170 | } 171 | if data.City == "" { 172 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "missing_city", nil) 173 | return 174 | } 175 | if data.State == "" { 176 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "missing_state", nil) 177 | return 178 | } 179 | if data.ZIP == "" { 180 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "missing_zip", nil) 181 | return 182 | } 183 | if data.Country == "" { 184 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "missing_country", nil) 185 | return 186 | } 187 | 188 | if data.Name == "" { 189 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "missing_name", nil) 190 | return 191 | } 192 | 193 | if data.Token.ID == "" { 194 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "missing_token", nil) 195 | return 196 | } 197 | 198 | planID := "plan_EPMR80fLcjkok9" 199 | 200 | var customer *stripe.Customer 201 | 202 | if user.StripeID == "" { 203 | /* Didn't find any custom, let's create it */ 204 | customerParams := &stripe.CustomerParams{ 205 | Email: stripe.String(user.Email), 206 | Shipping: &stripe.CustomerShippingDetailsParams{ 207 | Address: &stripe.AddressParams{ 208 | City: stripe.String(data.City), 209 | Country: stripe.String(data.Country), 210 | Line1: stripe.String(data.Address), 211 | PostalCode: stripe.String(strings.Replace(data.ZIP, "_", "", -1)), 212 | State: stripe.String(data.State), 213 | }, 214 | Name: stripe.String(data.Name), 215 | }, 216 | } 217 | customerParams.SetSource(data.Token.ID) 218 | 219 | var err error 220 | customer, err = utils.StripeClient.Customers.New(customerParams) 221 | if err != nil { 222 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 223 | return 224 | } 225 | } else { 226 | customer, _ = utils.StripeClient.Customers.Get(user.StripeID, nil) 227 | if customer.Subscriptions.TotalCount > 0 { 228 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "already_pro", nil) 229 | return 230 | } 231 | } 232 | 233 | subscription, err := utils.StripeClient.Subscriptions.New(&stripe.SubscriptionParams{ 234 | Customer: stripe.String(customer.ID), 235 | Items: []*stripe.SubscriptionItemsParams{ 236 | { 237 | Plan: stripe.String(planID), 238 | }, 239 | }, 240 | }) 241 | 242 | if err != nil { 243 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 244 | return 245 | } 246 | 247 | err = database.SetUserPro(user.ID, customer, subscription) 248 | if err != nil { 249 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 250 | return 251 | } 252 | 253 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 254 | return 255 | } 256 | 257 | // UpdateUser : update self data 258 | func UpdateUser(w http.ResponseWriter, r *http.Request) { 259 | 260 | id := utils.GetUserObjectID(r) 261 | 262 | user, err := database.GetUserByID(id) 263 | if err != nil { 264 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 265 | } 266 | 267 | var userData models.User 268 | err = json.NewDecoder(r.Body).Decode(&userData) 269 | if err != nil { 270 | utils.RespondWithJSON(w, http.StatusBadRequest, "error", nil) 271 | return 272 | } 273 | 274 | if len(userData.FullName) > 35 { 275 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_too_long", nil) 276 | return 277 | } 278 | 279 | if len(user.Country) > 100 { 280 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "country_too_long", nil) 281 | return 282 | } 283 | 284 | if len(user.City) > 100 { 285 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "city_too_long", nil) 286 | return 287 | } 288 | 289 | if len(user.Address) > 100 { 290 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "address_too_long", nil) 291 | return 292 | } 293 | if len(user.ZIP) > 100 { 294 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "zip_too_long", nil) 295 | return 296 | } 297 | if len(user.State) > 100 { 298 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "state_too_long", nil) 299 | return 300 | } 301 | 302 | if user.Email != userData.Email { 303 | //Email is now different 304 | //We need to check multiple things 305 | ok, errStr := database.VerifyEmail(userData.Email, r) 306 | if !ok { 307 | utils.RespondWithJSON(w, http.StatusNotAcceptable, errStr, nil) 308 | return 309 | } 310 | 311 | verification, err := database.CreateVerification(models.User{ 312 | ID: user.ID, 313 | Email: userData.Email, 314 | }) 315 | if err != nil { 316 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 317 | return 318 | } 319 | err = utils.SendVerificationMail(userData.Email, verification) 320 | if err != nil { 321 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 322 | return 323 | } 324 | 325 | } 326 | 327 | newUser, err := database.UpdateUser(user, userData) 328 | if err != nil { 329 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 330 | return 331 | } 332 | 333 | tokenString, err := utils.NewJWT(newUser, 48) 334 | if err != nil { 335 | utils.RespondWithJSON(w, http.StatusInternalServerError, err.Error(), nil) 336 | return 337 | } 338 | 339 | /* Send new token in payload */ 340 | utils.RespondWithJSON(w, http.StatusOK, "success", tokenString) 341 | return 342 | } 343 | 344 | // DeleteUser : remove user from db and also remove sheets & workbooks 345 | func DeleteUser(w http.ResponseWriter, r *http.Request) { 346 | id := utils.GetUserObjectID(r) 347 | 348 | sites, err := database.GetOwnedSites(id) 349 | if err != nil { 350 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 351 | return 352 | } 353 | if len(sites) > 0 { 354 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "delete_sites_first", nil) 355 | return 356 | } 357 | 358 | err = database.RemoveUser(id) 359 | if err != nil { 360 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 361 | return 362 | } 363 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 364 | return 365 | } 366 | 367 | // UpdateUserPassword : update user password 368 | func UpdateUserPassword(w http.ResponseWriter, r *http.Request) { 369 | id := utils.GetUserObjectID(r) 370 | 371 | user, err := database.GetUserByID(id) 372 | if err != nil { 373 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 374 | return 375 | } 376 | 377 | type passwordChangeRequest struct { 378 | LastPassword string `json:"last_password"` 379 | NewPassword string `json:"new_password"` 380 | } 381 | var request passwordChangeRequest 382 | err = json.NewDecoder(r.Body).Decode(&request) 383 | if err != nil { 384 | utils.RespondWithJSON(w, http.StatusBadRequest, err.Error(), nil) 385 | return 386 | } 387 | 388 | /* Check if database's password hash is same as user request */ 389 | err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.LastPassword)) 390 | if err != nil { 391 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "wrong_last_password", nil) 392 | return 393 | } 394 | 395 | /* It is */ 396 | /* Let's check if it a valid password */ 397 | /* Now let's hash the "new password" */ 398 | 399 | /* Check password length */ 400 | if len(request.NewPassword) < 8 { 401 | utils.RespondWithJSON(w, http.StatusUnprocessableEntity, "password_too_short", nil) 402 | return 403 | } 404 | if len(request.NewPassword) > 128 { 405 | utils.RespondWithJSON(w, http.StatusUnprocessableEntity, "password_too_long", nil) 406 | return 407 | } 408 | newPasswordHash, _ := bcrypt.GenerateFromPassword([]byte(request.NewPassword), 10) 409 | newPasswordHashString := string(newPasswordHash) 410 | 411 | err = database.UpdateUserPassword(id, newPasswordHashString) 412 | if err != nil { 413 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 414 | return 415 | } 416 | 417 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 418 | return 419 | } 420 | 421 | // VerifyUser : verify user email 422 | func VerifyUser(w http.ResponseWriter, r *http.Request) { 423 | vars := mux.Vars(r) 424 | strID := vars["id"] 425 | if bson.IsObjectIdHex(strID) { 426 | id := bson.ObjectIdHex(strID) 427 | verification, err := database.GetVerificationByID(id) 428 | if err != nil { 429 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 430 | return 431 | } 432 | if time.Until(verification.ExpireAt) > 0 { 433 | //Verification didn't expire 434 | user, err := database.GetUserByID(verification.UserID) 435 | if err != nil { 436 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 437 | return 438 | } 439 | err = database.VerifyUser(user, verification) 440 | if err != nil { 441 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 442 | return 443 | } 444 | 445 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 446 | return 447 | } 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /handlers/admin/sites.go: -------------------------------------------------------------------------------- 1 | package adminhandlers 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "regexp" 8 | 9 | "github.com/asaskevich/govalidator" 10 | "github.com/backpulse/core/constants" 11 | "github.com/backpulse/core/database" 12 | "github.com/backpulse/core/models" 13 | "github.com/backpulse/core/utils" 14 | "github.com/gorilla/mux" 15 | ) 16 | 17 | // GetSites : return list of sites for a given user 18 | func GetSites(w http.ResponseWriter, r *http.Request) { 19 | id := utils.GetUserObjectID(r) 20 | user, _ := database.GetUserByID(id) 21 | 22 | sites, _ := database.GetSitesOfUser(user) 23 | utils.RespondWithJSON(w, http.StatusOK, "success", sites) 24 | return 25 | } 26 | 27 | // GetOverview : return site overview informations 28 | func GetOverview(w http.ResponseWriter, r *http.Request) { 29 | vars := mux.Vars(r) 30 | name := vars["name"] 31 | 32 | site, _ := database.GetSiteByName(name) 33 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 34 | 35 | if !utils.IsAuthorized(site, user) { 36 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 37 | return 38 | } 39 | 40 | for _, c := range site.Collaborators { 41 | if user.Email == c.Email { 42 | site.Role = "collaborator" 43 | } 44 | } 45 | if site.Role == "" { 46 | site.Role = "owner" 47 | } 48 | 49 | files, _ := database.GetSiteFiles(site.ID) 50 | galleries, _ := database.GetGalleries(site.ID) 51 | albums, _ := database.GetAlbums(site.ID) 52 | videogroups, _ := database.GetVideoGroups(site.ID) 53 | articles, _ := database.GetVideoGroups(site.ID) 54 | projects, _ := database.GetProjects(site.ID) 55 | 56 | type overview struct { 57 | Files int `json:"files"` 58 | Galleries int `json:"galleries"` 59 | Albums int `json:"albums"` 60 | VideoGroups int `json:"video_groups"` 61 | Articles int `json:"articles"` 62 | Projects int `json:"projects"` 63 | 64 | TotalSize float64 `json:"total_size"` 65 | Name string `json:"name"` 66 | DisplayName string `json:"display_name"` 67 | Collaborators []models.Collaborator `json:"collaborators"` 68 | Modules []constants.Module `json:"modules"` 69 | } 70 | 71 | data := overview{ 72 | Files: len(files), 73 | Galleries: len(galleries), 74 | Albums: len(albums), 75 | VideoGroups: len(videogroups), 76 | Articles: len(articles), 77 | Projects: len(projects), 78 | DisplayName: site.DisplayName, 79 | Name: site.Name, 80 | Modules: site.Modules, 81 | Collaborators: site.Collaborators, 82 | 83 | TotalSize: database.GetSiteTotalSize(site.ID), 84 | } 85 | 86 | log.Println(database.GetSiteTotalSize(site.ID)) 87 | 88 | utils.RespondWithJSON(w, http.StatusOK, "success", data) 89 | return 90 | } 91 | 92 | // GetSite : return specific site 93 | func GetSite(w http.ResponseWriter, r *http.Request) { 94 | vars := mux.Vars(r) 95 | name := vars["name"] 96 | 97 | site, _ := database.GetSiteByName(name) 98 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 99 | 100 | if !utils.IsAuthorized(site, user) { 101 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 102 | return 103 | } 104 | 105 | for _, c := range site.Collaborators { 106 | if user.Email == c.Email { 107 | site.Role = "collaborator" 108 | } 109 | } 110 | if site.Role == "" { 111 | site.Role = "owner" 112 | } 113 | 114 | site.TotalSize = database.GetSiteTotalSize(site.ID) 115 | utils.RespondWithJSON(w, http.StatusOK, "success", site) 116 | return 117 | } 118 | 119 | // RemoveModule : remove module from site & all data associated 120 | func RemoveModule(w http.ResponseWriter, r *http.Request) { 121 | vars := mux.Vars(r) 122 | name := vars["name"] 123 | moduleName := vars["module"] 124 | log.Println(moduleName) 125 | 126 | site, _ := database.GetSiteByName(name) 127 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 128 | if !utils.IsAuthorized(site, user) { 129 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 130 | return 131 | } 132 | 133 | err := database.RemoveModule(site, moduleName) 134 | if err != nil { 135 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 136 | return 137 | } 138 | 139 | exists := false 140 | for _, m := range site.Modules { 141 | if m == constants.Module(moduleName) { 142 | exists = true 143 | } 144 | } 145 | if !exists { 146 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 147 | return 148 | } 149 | 150 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 151 | return 152 | } 153 | 154 | func AddModule(w http.ResponseWriter, r *http.Request) { 155 | vars := mux.Vars(r) 156 | name := vars["name"] 157 | moduleName := vars["module"] 158 | log.Println(moduleName) 159 | 160 | site, _ := database.GetSiteByName(name) 161 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 162 | if !utils.IsAuthorized(site, user) { 163 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 164 | return 165 | } 166 | 167 | /* Check if module exists */ 168 | exists := utils.CheckModuleExists(moduleName) 169 | if !exists { 170 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 171 | return 172 | } 173 | 174 | /* Check if module has not already been added */ 175 | for _, m := range site.Modules { 176 | if m == constants.Module(moduleName) { 177 | utils.RespondWithJSON(w, http.StatusConflict, "exists", nil) 178 | return 179 | } 180 | } 181 | 182 | err := database.AddModule(site, moduleName) 183 | if err != nil { 184 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 185 | return 186 | } 187 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 188 | return 189 | 190 | } 191 | 192 | func GetSiteModules(w http.ResponseWriter, r *http.Request) { 193 | vars := mux.Vars(r) 194 | name := vars["name"] 195 | 196 | site, _ := database.GetSiteByName(name) 197 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 198 | if !utils.IsAuthorized(site, user) { 199 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 200 | return 201 | } 202 | 203 | utils.RespondWithJSON(w, http.StatusOK, "success", site.Modules) 204 | return 205 | } 206 | 207 | // Favorite : favorite a site 208 | func Favorite(w http.ResponseWriter, r *http.Request) { 209 | vars := mux.Vars(r) 210 | name := vars["name"] 211 | 212 | site, _ := database.GetSiteByName(name) 213 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 214 | if !utils.IsAuthorized(site, user) { 215 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 216 | return 217 | } 218 | database.SetSiteFavorite(user, site.ID) 219 | 220 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 221 | return 222 | } 223 | 224 | // DeleteSite : remove all galleries, projects, photos, about, contact 225 | func DeleteSite(w http.ResponseWriter, r *http.Request) { 226 | vars := mux.Vars(r) 227 | name := vars["name"] 228 | id := utils.GetUserObjectID(r) 229 | site, _ := database.GetSiteByName(name) 230 | if site.OwnerID != id { 231 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 232 | return 233 | } 234 | 235 | // Delete photos 236 | 237 | galleries, _ := database.GetGalleries(site.ID) 238 | for _, g := range galleries { 239 | photos, _ := database.GetGalleryPhotos(g.ID) 240 | utils.RemoveGoogleCloudPhotos(photos) 241 | } 242 | 243 | err := database.DeleteSite(site) 244 | if err != nil { 245 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 246 | return 247 | } 248 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 249 | return 250 | } 251 | 252 | func GetCollaborators(w http.ResponseWriter, r *http.Request) { 253 | vars := mux.Vars(r) 254 | name := vars["name"] 255 | 256 | site, _ := database.GetSiteByName(name) 257 | user, _ := database.GetUserByID(utils.GetUserObjectID(r)) 258 | if !utils.IsAuthorized(site, user) { 259 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 260 | return 261 | } 262 | 263 | owner, _ := database.GetUserByID(site.OwnerID) 264 | site.Collaborators = append(site.Collaborators, models.Collaborator{ 265 | Email: owner.Email, 266 | Role: "owner", 267 | }) 268 | 269 | utils.RespondWithJSON(w, http.StatusOK, "success", site.Collaborators) 270 | return 271 | } 272 | 273 | func AddCollaborator(w http.ResponseWriter, r *http.Request) { 274 | vars := mux.Vars(r) 275 | name := vars["name"] 276 | email := vars["email"] 277 | 278 | id := utils.GetUserObjectID(r) 279 | site, _ := database.GetSiteByName(name) 280 | if site.OwnerID != id { 281 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 282 | return 283 | } 284 | 285 | if !govalidator.IsEmail(email) { 286 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "incorrect_email", nil) 287 | return 288 | } 289 | 290 | user, _ := database.GetUserByID(site.OwnerID) 291 | if email == user.Email { 292 | utils.RespondWithJSON(w, http.StatusConflict, "exists", nil) 293 | return 294 | } 295 | 296 | /* Check if email already exists */ 297 | for _, c := range site.Collaborators { 298 | if c.Email == email { 299 | utils.RespondWithJSON(w, http.StatusConflict, "exists", nil) 300 | return 301 | } 302 | } 303 | 304 | owner, err := database.GetUserByID(site.OwnerID) 305 | if err != nil { 306 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 307 | return 308 | } 309 | 310 | if len(site.Collaborators) > 0 && !owner.Professional { 311 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "upgrade_account", nil) 312 | return 313 | } 314 | 315 | err = database.AddCollaborator(site.ID, models.Collaborator{ 316 | Email: email, 317 | Role: "collaborator", 318 | }) 319 | if err != nil { 320 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 321 | return 322 | } 323 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 324 | return 325 | 326 | } 327 | 328 | func TransferSite(w http.ResponseWriter, r *http.Request) { 329 | vars := mux.Vars(r) 330 | name := vars["name"] 331 | email := vars["email"] 332 | 333 | id := utils.GetUserObjectID(r) 334 | site, _ := database.GetSiteByName(name) 335 | if site.OwnerID != id { 336 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 337 | return 338 | } 339 | 340 | exists := false 341 | for _, c := range site.Collaborators { 342 | if c.Email == email { 343 | exists = true 344 | break 345 | } 346 | } 347 | if !exists { 348 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 349 | return 350 | } 351 | 352 | isPremium := utils.HasPremiumFeatures(site, database.GetSiteTotalSize(site.ID)) 353 | 354 | nextOwner, err := database.GetUserByEmail(email) 355 | if err != nil { 356 | utils.RespondWithJSON(w, http.StatusNotFound, "not_found", nil) 357 | return 358 | } 359 | if isPremium && !nextOwner.Professional { 360 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "upgrade_account", nil) 361 | return 362 | } 363 | 364 | lastOwner, err := database.GetUserByID(site.OwnerID) 365 | if err != nil { 366 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 367 | return 368 | } 369 | 370 | err = database.TransferSite(site, lastOwner, nextOwner) 371 | if err != nil { 372 | log.Println(err) 373 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 374 | return 375 | } 376 | 377 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 378 | return 379 | } 380 | 381 | func RemoveCollaborator(w http.ResponseWriter, r *http.Request) { 382 | vars := mux.Vars(r) 383 | name := vars["name"] 384 | email := vars["email"] 385 | 386 | id := utils.GetUserObjectID(r) 387 | site, _ := database.GetSiteByName(name) 388 | if site.OwnerID != id { 389 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 390 | return 391 | } 392 | 393 | if !govalidator.IsEmail(email) { 394 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "incorrect_email", nil) 395 | return 396 | } 397 | 398 | user, _ := database.GetUserByID(site.OwnerID) 399 | if email == user.Email { 400 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 401 | return 402 | } 403 | 404 | exists := false 405 | for _, c := range site.Collaborators { 406 | if c.Email == email { 407 | exists = true 408 | } 409 | } 410 | if !exists { 411 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "not_found", nil) 412 | return 413 | } 414 | 415 | err := database.RemoveCollaborator(site.ID, email) 416 | if err != nil { 417 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 418 | return 419 | } 420 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 421 | return 422 | } 423 | 424 | // UpdateSite : update site informations 425 | func UpdateSite(w http.ResponseWriter, r *http.Request) { 426 | var siteData models.Site 427 | /* Parse json to models.User */ 428 | err := json.NewDecoder(r.Body).Decode(&siteData) 429 | if err != nil { 430 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 431 | return 432 | } 433 | vars := mux.Vars(r) 434 | name := vars["name"] 435 | 436 | id := utils.GetUserObjectID(r) 437 | site, _ := database.GetSiteByName(name) 438 | if site.OwnerID != id { 439 | utils.RespondWithJSON(w, http.StatusUnauthorized, "unauthorized", nil) 440 | return 441 | } 442 | 443 | if site.Name != siteData.Name { 444 | if len(siteData.Name) < 3 { 445 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_too_short", nil) 446 | return 447 | } 448 | if len(siteData.Name) > 30 { 449 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_too_long", nil) 450 | return 451 | } 452 | 453 | /* lowercase letters, dashes, digits */ 454 | var re = regexp.MustCompile(`([a-z-\d]+)`) 455 | var str = siteData.Name 456 | 457 | name := re.FindString(str) 458 | if name != siteData.Name { 459 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "incorrect_characters", nil) 460 | return 461 | } 462 | 463 | /* Check if site doesn't already exists */ 464 | exists := database.SiteExists(siteData.Name) 465 | if exists { 466 | utils.RespondWithJSON(w, http.StatusConflict, "site_exists", nil) 467 | return 468 | } 469 | } 470 | 471 | if len(siteData.DisplayName) > 60 { 472 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "display_name_too_long", nil) 473 | return 474 | } 475 | 476 | err = database.UpdateSite(site.ID, siteData) 477 | if err != nil { 478 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 479 | return 480 | } 481 | utils.RespondWithJSON(w, http.StatusOK, "success", nil) 482 | return 483 | 484 | } 485 | 486 | // CreateSite : add site to db 487 | func CreateSite(w http.ResponseWriter, r *http.Request) { 488 | var site models.Site 489 | /* Parse json to models.User */ 490 | err := json.NewDecoder(r.Body).Decode(&site) 491 | if err != nil { 492 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "error", nil) 493 | return 494 | } 495 | 496 | if len(site.Name) < 3 { 497 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_too_short", nil) 498 | return 499 | } 500 | if len(site.Name) > 30 { 501 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "name_too_long", nil) 502 | return 503 | } 504 | 505 | /* lowercase letters, dashes, digits */ 506 | var re = regexp.MustCompile(`([a-z-\d]+)`) 507 | var str = site.Name 508 | 509 | name := re.FindString(str) 510 | if name != site.Name { 511 | utils.RespondWithJSON(w, http.StatusNotAcceptable, "incorrect_characters", nil) 512 | return 513 | } 514 | 515 | /* Check if site doesn't already exists */ 516 | exists := database.SiteExists(site.Name) 517 | if exists { 518 | utils.RespondWithJSON(w, http.StatusConflict, "site_exists", nil) 519 | return 520 | } 521 | 522 | /* Looks good */ 523 | 524 | id := utils.GetUserObjectID(r) 525 | 526 | site.OwnerID = id 527 | site.DisplayName = site.Name 528 | 529 | site, err = database.CreateSite(site) 530 | log.Print(err) 531 | if err != nil { 532 | utils.RespondWithJSON(w, http.StatusInternalServerError, "error", nil) 533 | return 534 | } 535 | utils.RespondWithJSON(w, http.StatusOK, "success", site) 536 | return 537 | } 538 | --------------------------------------------------------------------------------