├── .env.example ├── .gitignore ├── Gopkg.lock ├── Gopkg.toml ├── databases └── mysql.go ├── docker-compose.yml ├── middlewares ├── auth.go ├── database.go ├── firebase.go └── youtube.go ├── models ├── favorite.go └── user.go ├── routes └── api.go ├── server.go ├── tools └── migrate.go └── web └── api ├── fetch_favorite_videos.go ├── fetch_most_popular_videos.go ├── fetch_related_videos.go ├── get_video.go ├── search_videos.go └── toggle_favorite_video.go /.env.example: -------------------------------------------------------------------------------- 1 | API_KEY= 2 | 3 | PROJECT_ID= 4 | KEY_JSON_PATH= 5 | 6 | DB_USERNAME= 7 | DB_PASSWORD= 8 | DB_HOST= 9 | DB_PORT= 10 | DB_DATABASE= 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | client_secret.json 15 | 16 | vendor/ 17 | 18 | .env 19 | 20 | !.gitkeep 21 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:0e5c0374276218c156d57fbc608902495efbc9fb5eac0309453fdef0a7326588" 6 | name = "cloud.google.com/go" 7 | packages = [ 8 | "compute/metadata", 9 | "firestore", 10 | "firestore/apiv1", 11 | "iam", 12 | "internal", 13 | "internal/btree", 14 | "internal/fields", 15 | "internal/optional", 16 | "internal/trace", 17 | "internal/version", 18 | "storage", 19 | ] 20 | pruneopts = "UT" 21 | revision = "ceeb313ad77b789a7fa5287b36a1d127b69b7093" 22 | version = "v0.44.3" 23 | 24 | [[projects]] 25 | digest = "1:23a95f2b1350788235f9b40d9c82d8dcabc975589201809f468cabccb314c7e1" 26 | name = "firebase.google.com/go" 27 | packages = [ 28 | ".", 29 | "auth", 30 | "db", 31 | "iid", 32 | "internal", 33 | "messaging", 34 | "storage", 35 | ] 36 | pruneopts = "UT" 37 | revision = "79d5dcdeff7f536eb11a549dd25579d88831d19e" 38 | version = "v3.9.0" 39 | 40 | [[projects]] 41 | digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55" 42 | name = "github.com/dgrijalva/jwt-go" 43 | packages = ["."] 44 | pruneopts = "UT" 45 | revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" 46 | version = "v3.2.0" 47 | 48 | [[projects]] 49 | digest = "1:68f0ad2c2af816280679ccac300f7b765fb6a6567fa949e07fb4c7125a6a6398" 50 | name = "github.com/golang/protobuf" 51 | packages = [ 52 | "proto", 53 | "protoc-gen-go/descriptor", 54 | "ptypes", 55 | "ptypes/any", 56 | "ptypes/duration", 57 | "ptypes/empty", 58 | "ptypes/struct", 59 | "ptypes/timestamp", 60 | "ptypes/wrappers", 61 | ] 62 | pruneopts = "UT" 63 | revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7" 64 | version = "v1.3.2" 65 | 66 | [[projects]] 67 | digest = "1:766102087520f9d54f2acc72bd6637045900ac735b4a419b128d216f0c5c4876" 68 | name = "github.com/googleapis/gax-go" 69 | packages = ["v2"] 70 | pruneopts = "UT" 71 | revision = "bd5b16380fd03dc758d11cef74ba2e3bc8b0e8c2" 72 | version = "v2.0.5" 73 | 74 | [[projects]] 75 | digest = "1:7fae9ec96d10b2afce0da23c378c8b3389319b7f92fa092f2621bba3078cfb4b" 76 | name = "github.com/hashicorp/golang-lru" 77 | packages = ["simplelru"] 78 | pruneopts = "UT" 79 | revision = "7f827b33c0f158ec5dfbba01bb0b14a4541fd81d" 80 | version = "v0.5.3" 81 | 82 | [[projects]] 83 | digest = "1:09424e4abeba1d2fc80c47c26347a3eed24f9cfd83173131dd048479ca6e041a" 84 | name = "github.com/jinzhu/gorm" 85 | packages = ["."] 86 | pruneopts = "UT" 87 | revision = "836fb2c19d84dac7b0272958dfb9af7cf0d0ade4" 88 | version = "v1.9.10" 89 | 90 | [[projects]] 91 | digest = "1:01ed62f8f4f574d8aff1d88caee113700a2b44c42351943fa73cc1808f736a50" 92 | name = "github.com/jinzhu/inflection" 93 | packages = ["."] 94 | pruneopts = "UT" 95 | revision = "f5c5f50e6090ae76a29240b61ae2a90dd810112e" 96 | version = "v1.0.0" 97 | 98 | [[projects]] 99 | digest = "1:ecd9aa82687cf31d1585d4ac61d0ba180e42e8a6182b85bd785fcca8dfeefc1b" 100 | name = "github.com/joho/godotenv" 101 | packages = ["."] 102 | pruneopts = "UT" 103 | revision = "23d116af351c84513e1946b527c88823e476be13" 104 | version = "v1.3.0" 105 | 106 | [[projects]] 107 | digest = "1:caa1da085cc12cd39d3ddb267434902f621050e1b46f7e9477b2b933df680193" 108 | name = "github.com/klauspost/compress" 109 | packages = [ 110 | "flate", 111 | "gzip", 112 | "zlib", 113 | ] 114 | pruneopts = "UT" 115 | revision = "2c38f6d2dc5a6a21b6851f2414f4e10adf8e3eee" 116 | version = "v1.8.1" 117 | 118 | [[projects]] 119 | digest = "1:923c4d7194b42e054b2eb8a6c62824ac55e23ececc1c7e48d4da69c971c55954" 120 | name = "github.com/klauspost/cpuid" 121 | packages = ["."] 122 | pruneopts = "UT" 123 | revision = "05a8198c0f5a27739aec358908d7e12c64ce6eb7" 124 | version = "v1.2.1" 125 | 126 | [[projects]] 127 | digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de" 128 | name = "github.com/konsorten/go-windows-terminal-sequences" 129 | packages = ["."] 130 | pruneopts = "UT" 131 | revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e" 132 | version = "v1.0.2" 133 | 134 | [[projects]] 135 | digest = "1:efd601ad1d1189884f37ed075fadb5bbd17f1f5acf5862827247c6da56d125ba" 136 | name = "github.com/labstack/echo" 137 | packages = [ 138 | ".", 139 | "middleware", 140 | ] 141 | pruneopts = "UT" 142 | revision = "38772c686c76b501f94bd6cd5b77f5842e93b559" 143 | version = "v3.3.10" 144 | 145 | [[projects]] 146 | digest = "1:e8e65826d60fd82637fb9df06ecd651191b599f4a8f72b86362012f288778320" 147 | name = "github.com/labstack/gommon" 148 | packages = [ 149 | "bytes", 150 | "color", 151 | "log", 152 | "random", 153 | ] 154 | pruneopts = "UT" 155 | revision = "4919956f6fb227b548f004da27c7c6b20fba499f" 156 | version = "v0.3.0" 157 | 158 | [[projects]] 159 | digest = "1:7c084e0e780596dd2a7e20d25803909a9a43689c153de953520dfbc0b0e51166" 160 | name = "github.com/mattn/go-colorable" 161 | packages = ["."] 162 | pruneopts = "UT" 163 | revision = "8029fb3788e5a4a9c00e415f586a6d033f5d38b3" 164 | version = "v0.1.2" 165 | 166 | [[projects]] 167 | digest = "1:36325ebb862e0382f2f14feef409ba9351271b89ada286ae56836c603d43b59c" 168 | name = "github.com/mattn/go-isatty" 169 | packages = ["."] 170 | pruneopts = "UT" 171 | revision = "e1f7b56ace729e4a73a29a6b4fac6cd5fcda7ab3" 172 | version = "v0.0.9" 173 | 174 | [[projects]] 175 | digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976" 176 | name = "github.com/sirupsen/logrus" 177 | packages = ["."] 178 | pruneopts = "UT" 179 | revision = "839c75faf7f98a33d445d181f3018b5c3409a45e" 180 | version = "v1.4.2" 181 | 182 | [[projects]] 183 | digest = "1:c468422f334a6b46a19448ad59aaffdfc0a36b08fdcc1c749a0b29b6453d7e59" 184 | name = "github.com/valyala/bytebufferpool" 185 | packages = ["."] 186 | pruneopts = "UT" 187 | revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7" 188 | version = "v1.0.0" 189 | 190 | [[projects]] 191 | digest = "1:15ad8a80098fcc7a194b9db6b26d74072a852e4faa957848c8118193d3c69230" 192 | name = "github.com/valyala/fasthttp" 193 | packages = [ 194 | ".", 195 | "fasthttputil", 196 | "stackless", 197 | ] 198 | pruneopts = "UT" 199 | revision = "e5f51c11919d4f66400334047b897ef0a94c6f3c" 200 | version = "v20180529" 201 | 202 | [[projects]] 203 | digest = "1:4d29fdc69817829d8c78473d61613d984ce59675110cee7a2f0314f332cc70a2" 204 | name = "github.com/valyala/fasttemplate" 205 | packages = ["."] 206 | pruneopts = "UT" 207 | revision = "8b5e4e491ab636663841c42ea3c5a9adebabaf36" 208 | version = "v1.0.1" 209 | 210 | [[projects]] 211 | digest = "1:bf33f7cd985e8e62eeef3b1985ec48f0f274e4083fa811596aafaf3af2947e83" 212 | name = "go.opencensus.io" 213 | packages = [ 214 | ".", 215 | "internal", 216 | "internal/tagencoding", 217 | "metric/metricdata", 218 | "metric/metricproducer", 219 | "plugin/ocgrpc", 220 | "plugin/ochttp", 221 | "plugin/ochttp/propagation/b3", 222 | "resource", 223 | "stats", 224 | "stats/internal", 225 | "stats/view", 226 | "tag", 227 | "trace", 228 | "trace/internal", 229 | "trace/propagation", 230 | "trace/tracestate", 231 | ] 232 | pruneopts = "UT" 233 | revision = "9c377598961b706d1542bd2d84d538b5094d596e" 234 | version = "v0.22.0" 235 | 236 | [[projects]] 237 | branch = "master" 238 | digest = "1:4f89a972160301f181540c6f408dd3110a633d7ea4339806c32a2f8a89dc8bb4" 239 | name = "golang.org/x/crypto" 240 | packages = [ 241 | "acme", 242 | "acme/autocert", 243 | ] 244 | pruneopts = "UT" 245 | revision = "60c769a6c58655dab1b9adac0d58967dd517cfba" 246 | 247 | [[projects]] 248 | branch = "master" 249 | digest = "1:37cf5c1105f46a9e4c5e6f0a2c9bcfc08e78462be487c2538cc3dccb73c36993" 250 | name = "golang.org/x/net" 251 | packages = [ 252 | "context", 253 | "context/ctxhttp", 254 | "http/httpguts", 255 | "http2", 256 | "http2/hpack", 257 | "idna", 258 | "internal/timeseries", 259 | "trace", 260 | ] 261 | pruneopts = "UT" 262 | revision = "74dc4d7220e7acc4e100824340f3e66577424772" 263 | 264 | [[projects]] 265 | branch = "master" 266 | digest = "1:31e33f76456ccf54819ab4a646cf01271d1a99d7712ab84bf1a9e7b61cd2031b" 267 | name = "golang.org/x/oauth2" 268 | packages = [ 269 | ".", 270 | "google", 271 | "internal", 272 | "jws", 273 | "jwt", 274 | ] 275 | pruneopts = "UT" 276 | revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33" 277 | 278 | [[projects]] 279 | branch = "master" 280 | digest = "1:47844666be86089349a441f5f0ece22f42a87a8cb8c9a31294c593f43209ad19" 281 | name = "golang.org/x/sys" 282 | packages = ["unix"] 283 | pruneopts = "UT" 284 | revision = "c7b8b68b14567162c6602a7c5659ee0f26417c18" 285 | 286 | [[projects]] 287 | digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405" 288 | name = "golang.org/x/text" 289 | packages = [ 290 | "collate", 291 | "collate/build", 292 | "internal/colltab", 293 | "internal/gen", 294 | "internal/language", 295 | "internal/language/compact", 296 | "internal/tag", 297 | "internal/triegen", 298 | "internal/ucd", 299 | "language", 300 | "secure/bidirule", 301 | "transform", 302 | "unicode/bidi", 303 | "unicode/cldr", 304 | "unicode/norm", 305 | "unicode/rangetable", 306 | ] 307 | pruneopts = "UT" 308 | revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475" 309 | version = "v0.3.2" 310 | 311 | [[projects]] 312 | digest = "1:e1577ad08a1260695cb95506f9d228a0574b0d491a6f93c715778bd168c02282" 313 | name = "google.golang.org/api" 314 | packages = [ 315 | "gensupport", 316 | "googleapi", 317 | "googleapi/internal/uritemplates", 318 | "googleapi/transport", 319 | "internal", 320 | "iterator", 321 | "option", 322 | "storage/v1", 323 | "transport", 324 | "transport/grpc", 325 | "transport/http", 326 | "transport/http/internal/propagation", 327 | "youtube/v3", 328 | ] 329 | pruneopts = "UT" 330 | revision = "feb0267beb8644f5088a03be4d5ec3f8c7020152" 331 | version = "v0.9.0" 332 | 333 | [[projects]] 334 | digest = "1:2c26b1c47556c0e5e73cdb05d8361c463737eee4baac35d38b40c728c3074a94" 335 | name = "google.golang.org/appengine" 336 | packages = [ 337 | ".", 338 | "internal", 339 | "internal/app_identity", 340 | "internal/base", 341 | "internal/datastore", 342 | "internal/log", 343 | "internal/modules", 344 | "internal/remote_api", 345 | "internal/socket", 346 | "internal/urlfetch", 347 | "socket", 348 | "urlfetch", 349 | ] 350 | pruneopts = "UT" 351 | revision = "5f2a59506353b8d5ba8cbbcd9f3c1f41f1eaf079" 352 | version = "v1.6.2" 353 | 354 | [[projects]] 355 | branch = "master" 356 | digest = "1:e9ae09c0e88c1206a1cb9b68cdabf9b5480623be373b4bde4c29acc347a93c1a" 357 | name = "google.golang.org/genproto" 358 | packages = [ 359 | "googleapis/api/annotations", 360 | "googleapis/firestore/v1", 361 | "googleapis/iam/v1", 362 | "googleapis/rpc/code", 363 | "googleapis/rpc/status", 364 | "googleapis/type/expr", 365 | "googleapis/type/latlng", 366 | ] 367 | pruneopts = "UT" 368 | revision = "24fa4b261c55da65468f2abfdae2b024eef27dfb" 369 | 370 | [[projects]] 371 | digest = "1:b6a61a85856e30b151ad9e958ac8ae3d1f719426bcefbd33289182a8219d299d" 372 | name = "google.golang.org/grpc" 373 | packages = [ 374 | ".", 375 | "balancer", 376 | "balancer/base", 377 | "balancer/grpclb", 378 | "balancer/grpclb/grpc_lb_v1", 379 | "balancer/roundrobin", 380 | "binarylog/grpc_binarylog_v1", 381 | "codes", 382 | "connectivity", 383 | "credentials", 384 | "credentials/alts", 385 | "credentials/alts/internal", 386 | "credentials/alts/internal/authinfo", 387 | "credentials/alts/internal/conn", 388 | "credentials/alts/internal/handshaker", 389 | "credentials/alts/internal/handshaker/service", 390 | "credentials/alts/internal/proto/grpc_gcp", 391 | "credentials/google", 392 | "credentials/internal", 393 | "credentials/oauth", 394 | "encoding", 395 | "encoding/proto", 396 | "grpclog", 397 | "internal", 398 | "internal/backoff", 399 | "internal/balancerload", 400 | "internal/binarylog", 401 | "internal/channelz", 402 | "internal/envconfig", 403 | "internal/grpcrand", 404 | "internal/grpcsync", 405 | "internal/syscall", 406 | "internal/transport", 407 | "keepalive", 408 | "metadata", 409 | "naming", 410 | "peer", 411 | "resolver", 412 | "resolver/dns", 413 | "resolver/passthrough", 414 | "serviceconfig", 415 | "stats", 416 | "status", 417 | "tap", 418 | ] 419 | pruneopts = "UT" 420 | revision = "6eaf6f47437a6b4e2153a190160ef39a92c7eceb" 421 | version = "v1.23.0" 422 | 423 | [solve-meta] 424 | analyzer-name = "dep" 425 | analyzer-version = 1 426 | input-imports = [ 427 | "firebase.google.com/go", 428 | "firebase.google.com/go/auth", 429 | "github.com/jinzhu/gorm", 430 | "github.com/joho/godotenv", 431 | "github.com/labstack/echo", 432 | "github.com/labstack/echo/middleware", 433 | "github.com/sirupsen/logrus", 434 | "github.com/valyala/fasthttp", 435 | "google.golang.org/api/option", 436 | "google.golang.org/api/youtube/v3", 437 | ] 438 | solver-name = "gps-cdcl" 439 | solver-version = 1 440 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [prune] 29 | go-tests = true 30 | unused-packages = true 31 | 32 | [[constraint]] 33 | name = "github.com/labstack/echo" 34 | version = "3.1.0" 35 | 36 | [[constraint]] 37 | name = "github.com/valyala/fasthttp" 38 | version = "20180529.0.0" 39 | 40 | [[constraint]] 41 | name = "google.golang.org/api" 42 | version = "0.9.0" 43 | 44 | [[constraint]] 45 | name = "github.com/sirupsen/logrus" 46 | version = "1.4.2" 47 | 48 | [[constraint]] 49 | name = "github.com/joho/godotenv" 50 | version = "1.3.0" 51 | 52 | [[constraint]] 53 | name = "firebase.google.com/go" 54 | version = "3.9.0" 55 | 56 | [[constraint]] 57 | name = "github.com/jinzhu/gorm" 58 | version = "1.9.10" 59 | -------------------------------------------------------------------------------- /databases/mysql.go: -------------------------------------------------------------------------------- 1 | package databases 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | _ "github.com/jinzhu/gorm/dialects/mysql" 6 | "github.com/joho/godotenv" 7 | "github.com/sirupsen/logrus" 8 | "os" 9 | ) 10 | 11 | func Connect() (db *gorm.DB, err error) { 12 | 13 | err = godotenv.Load() 14 | 15 | if err != nil { 16 | logrus.Fatal("Error loading .env file") 17 | } 18 | 19 | db, err = gorm.Open("mysql", 20 | os.Getenv("DB_USERNAME")+":"+os.Getenv("DB_PASSWORD")+ 21 | "@tcp("+os.Getenv("DB_HOST")+":"+os.Getenv("DB_PORT")+")/"+ 22 | os.Getenv("DB_DATABASE")+ 23 | "?charset=utf8mb4&parseTime=True&loc=Local") 24 | 25 | if err != nil { 26 | logrus.Fatal(err) 27 | } 28 | 29 | return db, err 30 | } 31 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | db: 5 | image: mysql:5.7 6 | command: > 7 | --character-set-server=utf8mb4 8 | --collation-server=utf8mb4_unicode_ci 9 | environment: 10 | - MYSQL_DATABASE=youtube 11 | - MYSQL_ROOT_PASSWORD=pass 12 | - MYSQL_USER=default 13 | - MYSQL_PASSWORD=password 14 | - TZ=Asia/Tokyo 15 | ports: 16 | - "4306:3306" 17 | volumes: 18 | - dbdata:/var/lib/mysql 19 | 20 | volumes: 21 | dbdata: 22 | -------------------------------------------------------------------------------- /middlewares/auth.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "context" 5 | "firebase.google.com/go/auth" 6 | "github.com/labstack/echo" 7 | "github.com/valyala/fasthttp" 8 | "strings" 9 | ) 10 | 11 | func verifyFirebaseIDToken(ctx echo.Context, auth *auth.Client) (*auth.Token, error) { 12 | headerAuth := ctx.Request().Header.Get("Authorization") 13 | token := strings.Replace(headerAuth, "Bearer ", "", 1) 14 | jwtToken, err := auth.VerifyIDToken(context.Background(), token) 15 | 16 | return jwtToken, err 17 | } 18 | 19 | func FirebaseGuard() echo.MiddlewareFunc { 20 | return func(next echo.HandlerFunc) echo.HandlerFunc { 21 | return func(c echo.Context) error { 22 | authClient := c.Get("firebase").(*auth.Client) 23 | jwtToken, err := verifyFirebaseIDToken(c, authClient) 24 | 25 | c.Set("auth", jwtToken) 26 | 27 | if err != nil { 28 | return c.JSON(fasthttp.StatusUnauthorized, "Not Authenticated") 29 | } 30 | 31 | if err := next(c); err != nil { 32 | return err 33 | } 34 | 35 | return nil 36 | } 37 | } 38 | } 39 | 40 | func FirebaseAuth() echo.MiddlewareFunc { 41 | return func(next echo.HandlerFunc) echo.HandlerFunc { 42 | return func(c echo.Context) error { 43 | authClient := c.Get("firebase").(*auth.Client) 44 | jwtToken, _ := verifyFirebaseIDToken(c, authClient) 45 | 46 | c.Set("auth", jwtToken) 47 | 48 | if err := next(c); err != nil { 49 | return err 50 | } 51 | 52 | return nil 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /middlewares/database.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/aiiro/youtube-manager-go/databases" 5 | "github.com/jinzhu/gorm" 6 | "github.com/labstack/echo" 7 | ) 8 | 9 | type DatabaseClient struct { 10 | DB *gorm.DB 11 | } 12 | 13 | func DatabaseService() echo.MiddlewareFunc { 14 | return func(next echo.HandlerFunc) echo.HandlerFunc { 15 | return func(c echo.Context) error { 16 | session, _ := databases.Connect() 17 | d := DatabaseClient{DB: session} 18 | 19 | defer d.DB.Close() 20 | 21 | // output sql query 22 | d.DB.LogMode(true) 23 | 24 | c.Set("dbs", &d) 25 | 26 | if err := next(c); err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /middlewares/firebase.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "context" 5 | firebase "firebase.google.com/go" 6 | "github.com/labstack/echo" 7 | "github.com/sirupsen/logrus" 8 | "google.golang.org/api/option" 9 | "os" 10 | ) 11 | 12 | func Firebase() echo.MiddlewareFunc { 13 | return func(next echo.HandlerFunc) echo.HandlerFunc { 14 | return func(c echo.Context) error { 15 | opt := option.WithCredentialsFile(os.Getenv("KEY_JSON_PATH")) 16 | config := &firebase.Config{ProjectID: os.Getenv("PROJECT_ID")} 17 | app, err := firebase.NewApp(context.Background(), config, opt) 18 | if err != nil { 19 | logrus.Fatalf("Error initializing firebase: %v\n", err) 20 | } 21 | 22 | auth, err := app.Auth(context.Background()) 23 | 24 | c.Set("firebase", auth) 25 | 26 | if err := next(c); err != nil { 27 | return err 28 | } 29 | 30 | return nil 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /middlewares/youtube.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "context" 5 | "github.com/labstack/echo" 6 | "github.com/sirupsen/logrus" 7 | "google.golang.org/api/option" 8 | "google.golang.org/api/youtube/v3" 9 | "os" 10 | ) 11 | 12 | func YouTubeService() echo.MiddlewareFunc { 13 | return func(next echo.HandlerFunc) echo.HandlerFunc { 14 | return func(c echo.Context) error { 15 | key := os.Getenv("API_KEY") 16 | 17 | ctx := context.Background() 18 | service, err := youtube.NewService(ctx, option.WithAPIKey(key)) 19 | if err != nil { 20 | logrus.Fatalf("Error creating YouTube service: %v", err) 21 | } 22 | 23 | c.Set("yts", service) 24 | 25 | if err := next(c); err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /models/favorite.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Favorite struct { 8 | ID uint `gorm:"primary_key"` 9 | UserId uint `json:"user_id"` 10 | VideoId string `json:"video_id"` 11 | CreatedAt time.Time `json:"-"` 12 | UpdatedAt time.Time `json:"-"` 13 | 14 | User User 15 | } 16 | -------------------------------------------------------------------------------- /models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type User struct { 8 | ID uint `gorm:"primary_key"` 9 | UID string `json:"-"` 10 | CreatedAt time.Time `json:"-"` 11 | UpdatedAt time.Time `json:"-"` 12 | DeletedAt *time.Time `sql:"index"json:"-"` 13 | 14 | Favorites []Favorite 15 | } 16 | -------------------------------------------------------------------------------- /routes/api.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/aiiro/youtube-manager-go/middlewares" 5 | "github.com/aiiro/youtube-manager-go/web/api" 6 | "github.com/labstack/echo" 7 | ) 8 | 9 | func Init(e *echo.Echo) { 10 | 11 | g := e.Group("/api") 12 | { 13 | g.GET("/popular", api.FetchMostPopularVideos()) 14 | g.GET("/video/:id", api.GetVideo(), middlewares.FirebaseAuth()) 15 | g.GET("/related/:id", api.FetchRelatedVideos()) 16 | g.GET("/search", api.SearchVideos()) 17 | } 18 | 19 | fg := g.Group("/favorite", middlewares.FirebaseGuard()) 20 | { 21 | fg.POST("/:id/toggle", api.ToggleFavoriteVideo()) 22 | fg.GET("", api.FetchFavoriteVideos()) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aiiro/youtube-manager-go/middlewares" 5 | "github.com/aiiro/youtube-manager-go/routes" 6 | "github.com/joho/godotenv" 7 | "github.com/labstack/echo" 8 | "github.com/labstack/echo/middleware" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func init() { 13 | err := godotenv.Load() 14 | if err != nil { 15 | logrus.Fatal("Error loading .env") 16 | } 17 | 18 | logrus.SetLevel(logrus.DebugLevel) 19 | logrus.SetFormatter(&logrus.JSONFormatter{}) 20 | } 21 | 22 | func main() { 23 | e := echo.New() 24 | 25 | //Middleware 26 | e.Use(middleware.Logger()) 27 | e.Use(middleware.CORS()) 28 | e.Use(middlewares.YouTubeService()) 29 | e.Use(middlewares.DatabaseService()) 30 | e.Use(middlewares.Firebase()) 31 | 32 | // Routes 33 | routes.Init(e) 34 | 35 | // Start server 36 | e.Logger.Fatal(e.Start(":8080")) 37 | } 38 | -------------------------------------------------------------------------------- /tools/migrate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/aiiro/youtube-manager-go/databases" 5 | "github.com/aiiro/youtube-manager-go/models" 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func main() { 10 | db, err := databases.Connect() 11 | defer db.Close() 12 | 13 | if err != nil { 14 | logrus.Fatal(err) 15 | } 16 | 17 | db.Debug().AutoMigrate(&models.User{}) 18 | db.Debug().AutoMigrate(&models.Favorite{}) 19 | } 20 | -------------------------------------------------------------------------------- /web/api/fetch_favorite_videos.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "firebase.google.com/go/auth" 5 | "github.com/aiiro/youtube-manager-go/middlewares" 6 | "github.com/aiiro/youtube-manager-go/models" 7 | "github.com/labstack/echo" 8 | "github.com/sirupsen/logrus" 9 | "github.com/valyala/fasthttp" 10 | "google.golang.org/api/youtube/v3" 11 | ) 12 | 13 | func FetchFavoriteVideos() echo.HandlerFunc { 14 | return func(c echo.Context) error { 15 | dbs := c.Get("dbs").(*middlewares.DatabaseClient) 16 | token := c.Get("auth").(*auth.Token) 17 | 18 | user := models.User{} 19 | dbs.DB.Table("users").Where(models.User{UID: token.UID}).First(&user) 20 | 21 | favorites := []models.Favorite{} 22 | dbs.DB.Model(&user).Related(&favorites) 23 | 24 | videoIds := "" 25 | for _, f := range favorites { 26 | if len(videoIds) == 0 { 27 | videoIds += f.VideoId 28 | } else { 29 | videoIds += "," + f.VideoId 30 | } 31 | } 32 | 33 | yts := c.Get("yts").(*youtube.Service) 34 | call := yts.Videos. 35 | List("id,snippet"). 36 | Id(videoIds). 37 | MaxResults(10) 38 | 39 | res, err := call.Do() 40 | if err != nil { 41 | logrus.Fatalf("Error calling Youtube API: %v", err) 42 | } 43 | 44 | return c.JSON(fasthttp.StatusOK, res) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /web/api/fetch_most_popular_videos.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/labstack/echo" 5 | "github.com/sirupsen/logrus" 6 | "github.com/valyala/fasthttp" 7 | "google.golang.org/api/youtube/v3" 8 | ) 9 | 10 | func FetchMostPopularVideos() echo.HandlerFunc { 11 | return func(c echo.Context) error { 12 | yts := c.Get("yts").(*youtube.Service) 13 | 14 | call := yts.Videos. 15 | List("id,snippet"). 16 | Chart("mostPopular"). 17 | MaxResults(3) 18 | 19 | pageToken := c.QueryParam("pageToken") 20 | if len(pageToken) > 0 { 21 | call = call.PageToken(pageToken) 22 | } 23 | 24 | res, err := call.Do() 25 | if err != nil { 26 | logrus.Fatalf("Error Youtube API: %v", err) 27 | } 28 | 29 | return c.JSON(fasthttp.StatusOK, res) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /web/api/fetch_related_videos.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/labstack/echo" 5 | "github.com/sirupsen/logrus" 6 | "github.com/valyala/fasthttp" 7 | "google.golang.org/api/youtube/v3" 8 | ) 9 | 10 | func FetchRelatedVideos() echo.HandlerFunc { 11 | return func(c echo.Context) error { 12 | yts := c.Get("yts").(*youtube.Service) 13 | 14 | videoId := c.Param("id") 15 | 16 | call := yts.Search. 17 | List("id,snippet"). 18 | RelatedToVideoId(videoId). 19 | Type("video"). 20 | MaxResults(3) 21 | 22 | res, err := call.Do() 23 | if err != nil { 24 | logrus.Fatalf("Error calling Youtube API: %v", err) 25 | } 26 | 27 | return c.JSON(fasthttp.StatusOK, res) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/api/get_video.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "firebase.google.com/go/auth" 5 | "github.com/aiiro/youtube-manager-go/middlewares" 6 | "github.com/aiiro/youtube-manager-go/models" 7 | "github.com/labstack/echo" 8 | "github.com/sirupsen/logrus" 9 | "github.com/valyala/fasthttp" 10 | "google.golang.org/api/youtube/v3" 11 | ) 12 | 13 | type VideoResponse struct { 14 | VideoList *youtube.VideoListResponse `json:"video_list"` 15 | IsFavorite bool `json:"is_favorite"` 16 | } 17 | 18 | func GetVideo() echo.HandlerFunc { 19 | return func(c echo.Context) error { 20 | yts := c.Get("yts").(*youtube.Service) 21 | dbs := c.Get("dbs").(*middlewares.DatabaseClient) 22 | token := c.Get("auth").(*auth.Token) 23 | 24 | videoId := c.Param("id") 25 | 26 | isFavorite := false 27 | if token != nil { 28 | favorite := models.Favorite{} 29 | isFavoriteNotFound := dbs.DB.Table("favorites"). 30 | Joins("INNER JOIN users ON users.id = favorites.user_id"). 31 | Where(models.User{UID: token.UID}). 32 | Where(models.Favorite{VideoId: videoId}). 33 | First(&favorite). 34 | RecordNotFound() 35 | 36 | logrus.Debug("isFavoriteNotFound: ", isFavoriteNotFound) 37 | if !isFavoriteNotFound { 38 | isFavorite = true 39 | } 40 | } 41 | 42 | call := yts.Videos. 43 | List("id,snippet"). 44 | Id(videoId) 45 | 46 | res, err := call.Do() 47 | if err != nil { 48 | logrus.Fatalf("Error calling Youtube API: %v", err) 49 | } 50 | 51 | v := VideoResponse{ 52 | VideoList: res, 53 | IsFavorite: isFavorite, 54 | } 55 | 56 | return c.JSON(fasthttp.StatusOK, v) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /web/api/search_videos.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/labstack/echo" 5 | "github.com/sirupsen/logrus" 6 | "github.com/valyala/fasthttp" 7 | "google.golang.org/api/youtube/v3" 8 | ) 9 | 10 | func SearchVideos() echo.HandlerFunc { 11 | return func(c echo.Context) error { 12 | yts := c.Get("yts").(*youtube.Service) 13 | 14 | query := c.QueryParam("q") 15 | 16 | call := yts.Search. 17 | List("id,snippet"). 18 | Q(query). 19 | MaxResults(3) 20 | 21 | pageToken := c.QueryParam("pageToken") 22 | if len(pageToken) > 0 { 23 | call = call.PageToken(pageToken) 24 | } 25 | 26 | res, err := call.Do() 27 | if err != nil { 28 | logrus.Fatalf("Error calling Youtube API: %v", err) 29 | } 30 | 31 | return c.JSON(fasthttp.StatusOK, res) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/api/toggle_favorite_video.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "firebase.google.com/go/auth" 5 | "github.com/aiiro/youtube-manager-go/middlewares" 6 | "github.com/aiiro/youtube-manager-go/models" 7 | "github.com/labstack/echo" 8 | "github.com/valyala/fasthttp" 9 | ) 10 | 11 | type ToggleFavoriteVideoResponse struct { 12 | VideoId string `json:"video_id"` 13 | IsFavorite bool `json:"is_favorite"` 14 | } 15 | 16 | func ToggleFavoriteVideo() echo.HandlerFunc { 17 | return func(c echo.Context) error { 18 | dbs := c.Get("dbs").(*middlewares.DatabaseClient) 19 | videoId := c.Param("id") 20 | token := c.Get("auth").(*auth.Token) 21 | user := models.User{} 22 | if dbs.DB.Table("users"). 23 | Where(models.User{UID: token.UID}).First(&user).RecordNotFound() { 24 | 25 | user = models.User{UID: token.UID} 26 | dbs.DB.Create(&user) 27 | } 28 | 29 | favorite := models.Favorite{} 30 | isFavorite := false 31 | if dbs.DB.Table("favorites"). 32 | Where(models.Favorite{UserId: user.ID, VideoId: videoId}).First(&favorite).RecordNotFound() { 33 | 34 | favorite = models.Favorite{UserId: user.ID, VideoId: videoId} 35 | dbs.DB.Create(&favorite) 36 | isFavorite = true 37 | } else { 38 | dbs.DB.Delete(&favorite) 39 | } 40 | 41 | res := ToggleFavoriteVideoResponse{ 42 | VideoId: videoId, 43 | IsFavorite: isFavorite, 44 | } 45 | 46 | return c.JSON(fasthttp.StatusOK, res) 47 | } 48 | } 49 | --------------------------------------------------------------------------------