├── .gitignore
├── api
├── .DS_Store
├── dbops
│ ├── conn.go
│ ├── internal.go
│ ├── api_test.go
│ └── api.go
├── response.go
├── utils
│ └── uuid.go
├── defs
│ ├── def.go
│ └── errs.go
├── auth.go
├── main.go
├── session
│ └── ops.go
└── handlers.go
├── asset
├── .DS_Store
├── Design.jpeg
├── web-snapshot.png
└── DB_support
│ ├── sessions.sql
│ ├── comments.sql
│ └── db.md
├── scheduler
├── .DS_Store
├── response.go
├── taskrunner
│ ├── defs.go
│ ├── trmain.go
│ ├── runner_test.go
│ ├── runner.go
│ └── tasks.go
├── dbops
│ ├── conn.go
│ ├── api.go
│ └── internal.go
├── main.go
└── handlers.go
├── static
├── templates
│ ├── .DS_Store
│ ├── img
│ │ └── preloader.jpg
│ ├── home.html
│ ├── userhome.html
│ └── scripts
│ │ └── home.js
└── video-server-frontend
│ ├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
│ ├── src
│ ├── setupTests.js
│ ├── App.test.js
│ ├── index.css
│ ├── reportWebVitals.js
│ ├── index.js
│ ├── App.js
│ ├── App.css
│ └── logo.svg
│ ├── .gitignore
│ ├── package.json
│ └── README.md
├── stream
├── defs.go
├── response.go
├── limiter.go
├── main.go
└── handler.go
├── web
├── defs.go
├── main.go
├── client.go
└── handlers.go
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | _Store
2 | .idea/*
3 | static/templates/.DS_Store
4 |
--------------------------------------------------------------------------------
/api/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/api/.DS_Store
--------------------------------------------------------------------------------
/asset/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/asset/.DS_Store
--------------------------------------------------------------------------------
/asset/Design.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/asset/Design.jpeg
--------------------------------------------------------------------------------
/scheduler/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/scheduler/.DS_Store
--------------------------------------------------------------------------------
/asset/web-snapshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/asset/web-snapshot.png
--------------------------------------------------------------------------------
/static/templates/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/static/templates/.DS_Store
--------------------------------------------------------------------------------
/static/templates/img/preloader.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/static/templates/img/preloader.jpg
--------------------------------------------------------------------------------
/static/video-server-frontend/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/stream/defs.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | const (
4 | VIDEO_DIR = "./videos/"
5 | MAX_UPLOAD_SIZE = 1024 * 1024 * 50 //50MB
6 | )
7 |
--------------------------------------------------------------------------------
/static/video-server-frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/static/video-server-frontend/public/favicon.ico
--------------------------------------------------------------------------------
/static/video-server-frontend/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/static/video-server-frontend/public/logo192.png
--------------------------------------------------------------------------------
/static/video-server-frontend/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgentMervin/video_server/HEAD/static/video-server-frontend/public/logo512.png
--------------------------------------------------------------------------------
/asset/DB_support/sessions.sql:
--------------------------------------------------------------------------------
1 | CREATE table sessions(
2 | session_id tinytext NOT NULL,
3 | TTL tinytext,
4 | login_name varchar(64),
5 | primary key (session_id)
6 | );
--------------------------------------------------------------------------------
/scheduler/response.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "io"
6 | )
7 |
8 | func sendResponse(w http.ResponseWriter, sc int, resp string) {
9 | w.WriteHeader(sc)
10 | io.WriteString(w, resp)
11 | }
--------------------------------------------------------------------------------
/stream/response.go:
--------------------------------------------------------------------------------
1 | package main
2 | import (
3 | "io"
4 | "net/http"
5 | )
6 |
7 | func sendErrorResponse(w http.ResponseWriter, sc int, errMsg string) {
8 | w.WriteHeader(sc)
9 | io.WriteString(w, errMsg)
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/asset/DB_support/comments.sql:
--------------------------------------------------------------------------------
1 | CREATE table comments(
2 | id varchar(64) NOT NULL,
3 | video_id varchar(64),
4 | author_id INT UNSIGNED,
5 | content TEXT,
6 | time DATETIME,
7 | primary key (id)
8 | );
--------------------------------------------------------------------------------
/static/video-server-frontend/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/scheduler/taskrunner/defs.go:
--------------------------------------------------------------------------------
1 | package taskrunner
2 |
3 | const (
4 | READY_TO_DISPATCH = "d"
5 | READY_TO_EXECUTE = "e"
6 | CLOSE = "c"
7 |
8 | VIDEO_PATH = "./videos/"
9 | )
10 |
11 | type controlChan chan string
12 |
13 | type dataChan chan interface{}
14 |
15 | type fn func(dc dataChan) error
--------------------------------------------------------------------------------
/static/video-server-frontend/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/api/dbops/conn.go:
--------------------------------------------------------------------------------
1 | package dbops
2 |
3 | import (
4 | "database/sql"
5 | _ "github.com/go-sql-driver/mysql"
6 | )
7 |
8 | var (
9 | dbConn *sql.DB
10 | err error
11 | )
12 |
13 | func init() {
14 | dbConn, err = sql.Open("mysql", "root:12345678@tcp(localhost:3306)/video_server?charset=utf8")
15 | if err != nil {
16 | panic(err.Error())
17 | }
18 | }
--------------------------------------------------------------------------------
/scheduler/dbops/conn.go:
--------------------------------------------------------------------------------
1 | package dbops
2 |
3 | import (
4 | "database/sql"
5 | _ "github.com/go-sql-driver/mysql"
6 | )
7 |
8 | var (
9 | dbConn *sql.DB
10 | err error
11 | )
12 |
13 | func init() {
14 | dbConn, err = sql.Open("mysql", "root:123!@#@tcp(localhost:3306)/video_server?charset=utf8")
15 | if err != nil {
16 | panic(err.Error())
17 | }
18 | }
--------------------------------------------------------------------------------
/static/video-server-frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/static/video-server-frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/static/video-server-frontend/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/scheduler/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "github.com/julienschmidt/httprouter"
6 | "github.com/avenssi/video_server/scheduler/taskrunner"
7 | )
8 |
9 | func RegisterHandlers() *httprouter.Router {
10 | router := httprouter.New()
11 |
12 | router.GET("/video-delete-record/:vid-id", vidDelRecHandler)
13 |
14 | return router
15 | }
16 |
17 | func main() {
18 | go taskrunner.Start()
19 | r := RegisterHandlers()
20 | http.ListenAndServe(":9001", r)
21 | }
--------------------------------------------------------------------------------
/scheduler/dbops/api.go:
--------------------------------------------------------------------------------
1 | package dbops
2 |
3 | import (
4 | "log"
5 | _ "github.com/go-sql-driver/mysql"
6 | )
7 |
8 | func AddVideoDeletionRecord(vid string) error {
9 | stmtIns, err := dbConn.Prepare("INSERT INTO video_del_rec (video_id) VALUES(?)")
10 | if err != nil {
11 | return err
12 | }
13 |
14 | _, err = stmtIns.Exec(vid)
15 | if err != nil {
16 | log.Printf("AddVideoDeletionRecord error: %v", err)
17 | return err
18 | }
19 |
20 | defer stmtIns.Close()
21 | return nil
22 | }
--------------------------------------------------------------------------------
/api/response.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "encoding/json"
6 | "net/http"
7 | "github.com/avenssi/video_server/api/defs"
8 | )
9 |
10 | func sendErrorResponse(w http.ResponseWriter, errResp defs.ErrResponse) {
11 | w.WriteHeader(errResp.HttpSC)
12 |
13 | resStr, _ := json.Marshal(&errResp.Error)
14 | io.WriteString(w, string(resStr))
15 | }
16 |
17 | func sendNormalResponse(w http.ResponseWriter, resp string, sc int) {
18 | w.WriteHeader(sc)
19 | io.WriteString(w, resp)
20 | }
--------------------------------------------------------------------------------
/api/utils/uuid.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/rand"
5 | "io"
6 | "fmt"
7 | )
8 |
9 | func NewUUID() (string, error) {
10 | uuid := make([]byte, 16)
11 | n, err := io.ReadFull(rand.Reader, uuid)
12 | if n != len(uuid) || err != nil {
13 | return "", err
14 | }
15 | // variant bits; see section 4.1.1
16 | uuid[8] = uuid[8]&^0xc0 | 0x80
17 | // version 4 (pseudo-random); see section 4.1.3
18 | uuid[6] = uuid[6]&^0xf0 | 0x40
19 | return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
20 | }
--------------------------------------------------------------------------------
/web/defs.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type ApiBody struct {
4 | Url string `json:"url"`
5 | Method string `json:"method"`
6 | ReqBody string `json:"req_body"`
7 | }
8 |
9 | type Err struct {
10 | Error string `json:"error"`
11 | ErrorCode string `json:"error_code"`
12 | }
13 |
14 | var (
15 | ErrorRequestNotRecognized = Err{Error: "api not recognized, bad request", ErrorCode: "001"}
16 | ErrorRequestBodyParseFailed = Err{Error: "request body is not correct", ErrorCode: "002"}
17 | ErrorInternalFaults = Err{Error: "internal server errror", ErrorCode: "003"}
18 | )
--------------------------------------------------------------------------------
/scheduler/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "github.com/julienschmidt/httprouter"
6 | "github.com/avenssi/video_server/scheduler/dbops"
7 | )
8 |
9 | func vidDelRecHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params){
10 | vid := p.ByName("vid-id")
11 |
12 | if len(vid) == 0 {
13 | sendResponse(w, 400, "video id should not be empty")
14 | return
15 | }
16 |
17 | err := dbops.AddVideoDeletionRecord(vid)
18 | if err != nil {
19 | sendResponse(w, 500, "Internal server error")
20 | return
21 | }
22 |
23 | sendResponse(w, 200, "")
24 | return
25 | }
--------------------------------------------------------------------------------
/static/video-server-frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/api/defs/def.go:
--------------------------------------------------------------------------------
1 | package defs
2 |
3 | //reqeusts
4 | type UserCredential struct {
5 | Username string `json:"user_name"`
6 | Pwd string `json:"pwd"`
7 | }
8 |
9 | //response
10 | type SignedUp struct {
11 | Success bool `json:"success"`
12 | SessionId string `json:"session_id"`
13 | }
14 |
15 | // Data model
16 | type VideoInfo struct {
17 | Id string
18 | AuthorId int
19 | Name string
20 | DisplayCtime string
21 | }
22 |
23 | type Comment struct {
24 | Id string
25 | VideoId string
26 | Author string
27 | Content string
28 | }
29 |
30 | type SimpleSession struct {
31 | Username string //login name
32 | TTL int64
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/static/video-server-frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/stream/limiter.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | )
6 |
7 | type ConnLimiter struct {
8 | concurrentConn int
9 | bucket chan int
10 | }
11 |
12 | func NewConnLimiter(cc int) *ConnLimiter {
13 | return &ConnLimiter {
14 | concurrentConn: cc,
15 | bucket: make(chan int, cc),
16 | }
17 | }
18 |
19 | func (cl *ConnLimiter) GetConn() bool {
20 | if len(cl.bucket) >= cl.concurrentConn {
21 | log.Printf("Reached the rate limitation.")
22 | return false
23 | }
24 |
25 | cl.bucket <- 1
26 | return true
27 | }
28 |
29 | func (cl *ConnLimiter) ReleaseConn() {
30 | c :=<- cl.bucket
31 | log.Printf("New connction coming: %d", c)
32 | }
33 |
--------------------------------------------------------------------------------
/web/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | // "html/template"
6 | "github.com/julienschmidt/httprouter"
7 | )
8 |
9 | func RegisterHandler() *httprouter.Router {
10 | router:=httprouter.New()
11 | router.GET("/", homeHandler)
12 | router.POST("/", homeHandler)
13 | router.GET("/userhome", userHomeHandler)
14 | router.POST("/userhome", userHomeHandler)
15 | router.POST("/api", apiHandler)
16 | router.POST("/upload/:vid-id", proxyHandler)
17 | router.ServeFiles("/statics/*filepath", http.Dir("./template"))
18 | return router
19 | }
20 |
21 |
22 | func main() {
23 | r:=RegisterHandler()
24 | http.ListenAndServe(":8080", r)
25 | }
26 |
--------------------------------------------------------------------------------
/static/video-server-frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from './logo.svg';
2 | import './App.css';
3 |
4 | function App() {
5 | return (
6 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/api/defs/errs.go:
--------------------------------------------------------------------------------
1 | package defs
2 |
3 | type Err struct{
4 | Error string `json:"error"`
5 | ErrCode string `json:"err_code"`
6 | }
7 |
8 | type ErrResponse struct {
9 | HttpSC int
10 | Error Err
11 | }
12 |
13 | var (
14 | ErrorRequestBodyParseFailed = ErrResponse{HttpSC: 400, Error: Err{Error: "Request body is not correct", ErrCode: "001"}}
15 | ErrorNotAuthUser = ErrResponse{HttpSC: 401, Error: Err{Error: "User anthentication failed.", ErrCode: "002"}}
16 | ErrorDBError = ErrResponse{HttpSC: 500, Error: Err{Error: "DB ops failed", ErrCode: "003"}}
17 | ErrorInternalFaults = ErrResponse{HttpSC: 500, Error: Err{Error: "Internal service error", ErrCode: "004"}}
18 | )
19 |
20 |
--------------------------------------------------------------------------------
/scheduler/taskrunner/trmain.go:
--------------------------------------------------------------------------------
1 | package taskrunner
2 |
3 | import (
4 | "time"
5 | //"log"
6 | )
7 |
8 | type Worker struct {
9 | ticker *time.Ticker
10 | runner *Runner
11 | }
12 |
13 | func NewWorker(interval time.Duration, r *Runner) *Worker {
14 | return &Worker {
15 | ticker: time.NewTicker(interval * time.Second),
16 | runner: r,
17 | }
18 | }
19 |
20 | func (w *Worker) startWorker() {
21 | for {
22 | select {
23 | case <- w.ticker.C:
24 | go w.runner.StartAll()
25 | }
26 | }
27 | }
28 |
29 | func Start() {
30 | // Start video file cleaning
31 | r := NewRunner(3, true, VideoClearDispatcher, VideoClearExecutor)
32 | w := NewWorker(3, r)
33 | go w.startWorker()
34 | }
35 |
36 |
37 |
--------------------------------------------------------------------------------
/scheduler/taskrunner/runner_test.go:
--------------------------------------------------------------------------------
1 | package taskrunner
2 |
3 | import (
4 | "testing"
5 | "log"
6 | "time"
7 | "errors"
8 | )
9 |
10 | func TestRunner(t *testing.T) {
11 | d := func(dc dataChan) error {
12 | for i := 0; i < 30; i++ {
13 | dc <- i;
14 | log.Printf("Dispatcher sent: %v", i)
15 | }
16 |
17 | return nil
18 | }
19 |
20 | e := func(dc dataChan) error {
21 | forloop:
22 | for {
23 | select {
24 | case d :=<- dc:
25 | log.Printf("Executor received: %v", d)
26 | default:
27 | break forloop
28 | }
29 | }
30 |
31 | return errors.New("Executor")
32 | }
33 |
34 | runner := NewRunner(30, false, d, e)
35 | go runner.StartAll()
36 | time.Sleep(3 * time.Second)
37 | }
--------------------------------------------------------------------------------
/static/video-server-frontend/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/api/auth.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "video_server/api/session"
6 | "video_server/api/defs"
7 |
8 | )
9 |
10 | var HEADER_FIELD_SESSION = "X-Session-Id"
11 | var HEADER_FIELD_UNAME = "X-User-Name"
12 |
13 |
14 | func validateUserSession(r *http.Request) bool {
15 | sid := r.Header.Get(HEADER_FIELD_SESSION)
16 | if len(sid) == 0 {
17 | return false
18 | }
19 |
20 | uname, ok := session.IsSessionExpired(sid)
21 | if ok {
22 | return false
23 | }
24 |
25 | r.Header.Add(HEADER_FIELD_UNAME, uname)
26 | return true
27 | }
28 |
29 | func ValidateUser(w http.ResponseWriter, r *http.Request) bool {
30 | uname := r.Header.Get(HEADER_FIELD_UNAME)
31 | if len(uname) == 0 {
32 | sendErrorResponse(w, defs.ErrorNotAuthUser)
33 | return false
34 | }
35 |
36 | return true
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/api/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/julienschmidt/httprouter"
5 | "net/http"
6 | "log"
7 | )
8 |
9 | type middleWareHandler struct{
10 | r *httprouter.Router
11 | }
12 | func NewMiddleWareHandler(){
13 |
14 | }
15 | func RegisterHandlers() *httprouter.Router {
16 | log.Printf("preparing to post request\n")
17 | router:=httprouter.New()
18 | router.POST("/user", CreateUser)
19 | router.POST("/user/:username", Login)
20 | router.GET("/user/:username", GetUserInfo)
21 | router.POST("/user/:username/videos", AddNewVideo)
22 | router.GET("/user/:username/videos", ListAllVideos)
23 | router.DELETE("/user/:username/videos/:vid-id", DeleteVideo)
24 | router.POST("/videos/:vid-id/comments", PostComment)
25 | router.GET("/videos/:vid-id/comments", ShowComments)
26 | return router
27 | }
28 |
29 | func main(){
30 | r :=RegisterHandlers()
31 | http.ListenAndServe(":8000",r)
32 |
33 | }
34 |
35 | //handler -> validation{1.request, 2.user}->business logic -> response.
--------------------------------------------------------------------------------
/static/video-server-frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "video-server-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "react": "^17.0.2",
10 | "react-dom": "^17.0.2",
11 | "react-scripts": "4.0.3",
12 | "web-vitals": "^1.0.1"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": [
22 | "react-app",
23 | "react-app/jest"
24 | ]
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/stream/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "github.com/julienschmidt/httprouter"
6 | )
7 |
8 | type middleWareHandler struct {
9 | r *httprouter.Router
10 | l *ConnLimiter
11 | }
12 |
13 | func NewMiddleWareHandler(r *httprouter.Router, cc int) http.Handler {
14 | m := middleWareHandler{}
15 | m.r = r
16 | m.l = NewConnLimiter(cc)
17 | return m
18 | }
19 |
20 | func RegisterHandlers() *httprouter.Router {
21 | router := httprouter.New()
22 |
23 | router.GET("/videos/:vid-id", streamHandler)
24 |
25 | router.POST("/upload/:vid-id", uploadHandler)
26 |
27 | router.GET("/testpage", testPageHandler)
28 |
29 | return router
30 | }
31 |
32 | func (m middleWareHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
33 | if !m.l.GetConn() {
34 | sendErrorResponse(w, http.StatusTooManyRequests, "Too many requests")
35 | return
36 | }
37 |
38 | m.r.ServeHTTP(w, r)
39 | defer m.l.ReleaseConn()
40 | }
41 |
42 | func main() {
43 | r := RegisterHandlers()
44 | mh := NewMiddleWareHandler(r, 2)
45 | http.ListenAndServe(":9000", mh)
46 | }
--------------------------------------------------------------------------------
/scheduler/dbops/internal.go:
--------------------------------------------------------------------------------
1 | package dbops
2 |
3 | import (
4 | "log"
5 | _ "github.com/go-sql-driver/mysql"
6 | )
7 |
8 | func ReadVideoDeletionRecord(count int) ([]string, error) {
9 | stmtOut, err := dbConn.Prepare("SELECT video_id FROM video_del_rec LIMIT ?")
10 |
11 | var ids []string
12 |
13 | if err != nil {
14 | return ids, err
15 | }
16 |
17 | rows, err := stmtOut.Query(count)
18 | if err != nil {
19 | log.Printf("Query VideoDeletionRecord error: %v", err)
20 | return ids, err
21 | }
22 |
23 | for rows.Next() {
24 | var id string
25 | if err := rows.Scan(&id); err != nil {
26 | return ids, err
27 | }
28 |
29 | ids = append(ids, id)
30 | }
31 |
32 | defer stmtOut.Close()
33 | return ids, nil
34 | }
35 |
36 | func DelVideoDeletionRecord(vid string) error {
37 | stmtDel, err := dbConn.Prepare("DELETE FROM video_del_rec WHERE video_id=?")
38 | if err != nil {
39 | return err
40 | }
41 |
42 | _, err = stmtDel.Exec(vid)
43 | if err != nil {
44 | log.Printf("Deleting VideoDeletionRecord error: %v", err)
45 | return err
46 | }
47 |
48 | defer stmtDel.Close()
49 | return nil
50 | }
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Share+: video-sharing platform
3 |
4 | A streaming video website for internal use. User can watch/upload/download videos from the website. Comments are also permitted if the owner(s) of the video agrees.
5 |
6 | ## Features
7 | - Very simple to install and use;
8 | - Pure Golang, high performance, and cross-platform;
9 | - Supports commonly used transmission protocols, file formats, and encoding formats;
10 |
11 |
12 |
13 | ## Architecture
14 | 
15 |
16 | ## API Design
17 | Using HTTP Protocol to fulfill the operation on resource.
18 | Three types of APIs:
19 | - USER API: return states of each user.
20 | - RESOURCE API: returns the states of video(s).
21 | - COMMENT API: returns all the comments under one specific video/
22 |
23 | ## Streaming Server Design
24 | Prerequisite:
25 | [bitbucket](https://godoc.org/github.com/DavidCai1993/token-bucket)
26 |
27 | - UDP protocol to implement file uploading
28 | - Token Bucket to control rate limit
29 |
30 | ## Scheduler Design
31 | [**Channels are the pipes that connect concurrent goroutines.**](https://tour.golang.org/concurrency/2)
32 |
33 | - Asynchronous Delete
34 | - Producer-Consumer Model
35 | - Timer: Run and Stop
36 |
37 |
38 | ## Future goals
39 |
40 | Cloud native Optimization
41 |
42 |
--------------------------------------------------------------------------------
/api/session/ops.go:
--------------------------------------------------------------------------------
1 | package session
2 |
3 | import (
4 | "time"
5 | "sync"
6 | "video_server/api/defs"
7 | "video_server/api/dbops"
8 | "video_server/api/utils"
9 | )
10 |
11 | var sessionMap *sync.Map
12 |
13 | func init() {
14 | sessionMap = &sync.Map{}
15 | }
16 |
17 | func nowInMilli() int64{
18 | return time.Now().UnixNano()/1000000
19 | }
20 |
21 | func deleteExpiredSession(sid string) {
22 | sessionMap.Delete(sid)
23 | dbops.DeleteSession(sid)
24 | }
25 |
26 | func LoadSessionsFromDB() {
27 | r, err := dbops.RetrieveAllSessions()
28 | if err != nil {
29 | return
30 | }
31 |
32 | r.Range(func(k, v interface{}) bool{
33 | ss := v.(*defs.SimpleSession)
34 | sessionMap.Store(k, ss)
35 | return true
36 | })
37 | }
38 |
39 | func GenerateNewSessionId(un string) string {
40 | id, _ := utils.NewUUID()
41 | ct := nowInMilli()
42 | ttl := ct + 30 * 60 * 1000// Severside session valid time: 30 min
43 |
44 | ss := &defs.SimpleSession{Username: un, TTL: ttl}
45 | sessionMap.Store(id, ss)
46 | dbops.InsertSession(id, ttl, un)
47 |
48 | return id
49 | }
50 |
51 | func IsSessionExpired(sid string) (string, bool) {
52 | ss, ok := sessionMap.Load(sid)
53 | if ok {
54 | ct := nowInMilli()
55 | if ss.(*defs.SimpleSession).TTL < ct {
56 | deleteExpiredSession(sid)
57 | return "", true
58 | }
59 |
60 | return ss.(*defs.SimpleSession).Username, false
61 | }
62 |
63 | return "", true
64 | }
--------------------------------------------------------------------------------
/asset/DB_support/db.md:
--------------------------------------------------------------------------------
1 | ##Local Database Connection Test
2 | ```
3 | go test -v
4 |
5 | === RUN TestUserWorkFlow
6 | === RUN TestUserWorkFlow/Add
7 | === RUN TestUserWorkFlow/Get
8 | === RUN TestUserWorkFlow/Del
9 | === RUN TestUserWorkFlow/Reget
10 | --- PASS: TestUserWorkFlow (0.00s)
11 | --- PASS: TestUserWorkFlow/Add (0.00s)
12 | --- PASS: TestUserWorkFlow/Get (0.00s)
13 | --- PASS: TestUserWorkFlow/Del (0.00s)
14 | --- PASS: TestUserWorkFlow/Reget (0.00s)
15 | === RUN TestVideoWorkFlow
16 | === RUN TestVideoWorkFlow/PrepareUser
17 | === RUN TestVideoWorkFlow/AddVideo
18 | === RUN TestVideoWorkFlow/GetVideo
19 | === RUN TestVideoWorkFlow/DelVideo
20 | === RUN TestVideoWorkFlow/RegetVideo
21 | --- PASS: TestVideoWorkFlow (0.03s)
22 | --- PASS: TestVideoWorkFlow/PrepareUser (0.00s)
23 | --- PASS: TestVideoWorkFlow/AddVideo (0.00s)
24 | --- PASS: TestVideoWorkFlow/GetVideo (0.00s)
25 | --- PASS: TestVideoWorkFlow/DelVideo (0.00s)
26 | --- PASS: TestVideoWorkFlow/RegetVideo (0.00s)
27 | === RUN TestComments
28 | === RUN TestComments/AddUser
29 | === RUN TestComments/AddCommnets
30 | === RUN TestComments/ListComments
31 | --- PASS: TestComments (0.04s)
32 | --- PASS: TestComments/AddUser (0.00s)
33 | --- PASS: TestComments/AddCommnets (0.00s)
34 | --- PASS: TestComments/ListComments (0.00s)
35 | PASS
36 | ok video_server/api/dbops 0.312s
37 |
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/scheduler/taskrunner/runner.go:
--------------------------------------------------------------------------------
1 | package taskrunner
2 |
3 | import (
4 | )
5 |
6 | type Runner struct {
7 | Controller controlChan
8 | Error controlChan
9 | Data dataChan
10 | dataSize int
11 | longLived bool
12 | Dispatcher fn
13 | Executor fn
14 | }
15 |
16 | func NewRunner(size int, longlived bool, d fn, e fn) *Runner {
17 | return &Runner {
18 | Controller: make(chan string, 1),
19 | Error: make(chan string, 1),
20 | Data: make(chan interface{}, size),
21 | longLived: longlived,
22 | dataSize: size,
23 | Dispatcher: d,
24 | Executor: e,
25 | }
26 | }
27 |
28 | func (r *Runner) startDispatch() {
29 | defer func() {
30 | if !r.longLived {
31 | close(r.Controller)
32 | close(r.Data)
33 | close(r.Error)
34 | }
35 | }()
36 |
37 | for {
38 | select {
39 | case c :=<- r.Controller:
40 | if c == READY_TO_DISPATCH {
41 | err := r.Dispatcher(r.Data)
42 | if err != nil {
43 | r.Error <- CLOSE
44 | } else {
45 | r.Controller <- READY_TO_EXECUTE
46 | }
47 | }
48 |
49 | if c == READY_TO_EXECUTE {
50 | err := r.Executor(r.Data)
51 | if err != nil {
52 | r.Error <- CLOSE
53 | } else {
54 | r.Controller <- READY_TO_DISPATCH
55 | }
56 | }
57 | case e :=<- r.Error:
58 | if e == CLOSE {
59 | return
60 | }
61 | default:
62 |
63 | }
64 | }
65 | }
66 |
67 | func (r *Runner) StartAll() {
68 | r.Controller <- READY_TO_DISPATCH
69 | r.startDispatch()
70 | }
--------------------------------------------------------------------------------
/scheduler/taskrunner/tasks.go:
--------------------------------------------------------------------------------
1 | package taskrunner
2 |
3 | import (
4 | "os"
5 | "errors"
6 | "log"
7 | "sync"
8 | "video_server/scheduler/dbops"
9 | )
10 |
11 | func deleteVideo(vid string) error {
12 | err := os.Remove(VIDEO_PATH + vid)
13 |
14 | if err != nil && !os.IsNotExist(err) {
15 | log.Printf("Deleting video error: %v", err)
16 | return err
17 | }
18 |
19 | return nil
20 | }
21 |
22 | func VideoClearDispatcher(dc dataChan) error {
23 | res, err := dbops.ReadVideoDeletionRecord(3)
24 | if err != nil {
25 | log.Printf("Video clear dispatcher error: %v", err)
26 | return err
27 | }
28 |
29 | if len(res) == 0 {
30 | return errors.New("All tasks finished")
31 | }
32 |
33 | for _, id := range res {
34 | dc <- id
35 | }
36 |
37 | return nil
38 | }
39 |
40 | func VideoClearExecutor(dc dataChan) error {
41 | errMap := &sync.Map{}
42 | var err error
43 |
44 | forloop:
45 | for {
46 | select {
47 | case vid :=<- dc:
48 | go func(id interface{}) {
49 | if err := deleteVideo(id.(string)); err != nil {
50 | errMap.Store(id, err)
51 | return
52 | }
53 | if err := dbops.DelVideoDeletionRecord(id.(string)); err != nil {
54 | errMap.Store(id, err)
55 | return
56 | }
57 | }(vid)
58 | default:
59 | break forloop
60 | }
61 | }
62 |
63 | errMap.Range(func(k, v interface{}) bool {
64 | err = v.(error)
65 | if err != nil {
66 | return false
67 | }
68 | return true
69 | })
70 |
71 | return err
72 | }
73 |
--------------------------------------------------------------------------------
/web/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import(
4 | "log"
5 | "net/http"
6 | "bytes"
7 | "io"
8 | "io/ioutil"
9 | "encoding/json"
10 | )
11 |
12 | var httpClient *http.Client
13 |
14 | func init(){
15 | httpClient= &http.Client{}
16 | }
17 |
18 | func request(b *ApiBody, w http.ResponseWriter, r *http.Request) {
19 | var resp *http.Response
20 | var err error
21 |
22 | switch b.Method {
23 | case http.MethodGet:
24 | req, _:=http.NewRequest("GET", b.Url, nil)
25 | req.Header = r.Header
26 | resp, err= httpClient.Do(req)
27 | if err!=nil{
28 | log.Printf(err.Error())
29 | return
30 | }
31 | normalResponse(w, resp)
32 | case http.MethodPost:
33 | req, _:=http.NewRequest("POST", b.Url, bytes.NewBuffer([]byte(b.ReqBody)))
34 | req.Header = r.Header
35 | resp, err = httpClient.Do(req)
36 | if err!=nil {
37 | log.Printf(err.Error())
38 | return
39 | }
40 | normalResponse(w, resp)
41 | case http.MethodDelete:
42 | req, _:=http.NewRequest("Delete", b.Url, nil)
43 | req.Header = r.Header
44 | resp, err = httpClient.Do(req)
45 | if err!=nil{
46 | log.Printf(err.Error())
47 | return
48 | }
49 | normalResponse(w, resp)
50 | default:
51 | w.WriteHeader(http.StatusBadRequest)
52 | io.WriteString(w, "Bad api request")
53 | return
54 | }
55 |
56 | }
57 |
58 | func normalResponse(w http.ResponseWriter, r *http.Response) {
59 | res, err:=ioutil.ReadAll(r.Body)
60 | if err!=nil{
61 | re, _:=json.Marshal(ErrorInternalFaults)
62 | w.WriteHeader(500)
63 | io.WriteString(w, string(re))
64 | return
65 | }
66 |
67 | w.WriteHeader(r.StatusCode)
68 | io.WriteString(w, string(res))
69 | }
--------------------------------------------------------------------------------
/static/video-server-frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/stream/handler.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io"
5 | "os"
6 | "net/http"
7 | "html/template"
8 | "io/ioutil"
9 | "time"
10 | "log"
11 | "github.com/julienschmidt/httprouter"
12 | )
13 |
14 | func testPageHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
15 | t, _ := template.ParseFiles("./videos/upload.html")
16 |
17 | t.Execute(w, nil)
18 | }
19 |
20 |
21 | func streamHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
22 | vid := p.ByName("vid-id")
23 | vl := VIDEO_DIR + vid
24 |
25 | video, err := os.Open(vl)
26 | if err != nil {
27 | log.Printf("Error when try to open file: %v", err)
28 | sendErrorResponse(w, http.StatusInternalServerError, "Internal Error")
29 | return
30 | }
31 |
32 | w.Header().Set("Content-Type", "video/mp4")
33 | http.ServeContent(w, r, "", time.Now(), video)
34 |
35 | defer video.Close()
36 | }
37 |
38 | func uploadHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
39 | r.Body = http.MaxBytesReader(w, r.Body, MAX_UPLOAD_SIZE)
40 | if err := r.ParseMultipartForm(MAX_UPLOAD_SIZE); err != nil {
41 | sendErrorResponse(w, http.StatusBadRequest, "File is too big")
42 | return
43 | }
44 |
45 | file, _, err := r.FormFile("file")
46 | if err != nil {
47 | log.Printf("Error when try to get file: %v", err)
48 | sendErrorResponse(w, http.StatusInternalServerError, "Internal Error")
49 | return
50 | }
51 |
52 | data, err := ioutil.ReadAll(file)
53 | if err != nil {
54 | log.Printf("Read file error: %v", err)
55 | sendErrorResponse(w, http.StatusInternalServerError, "Internal Error")
56 | }
57 |
58 | fn := p.ByName("vid-id")
59 | err = ioutil.WriteFile(VIDEO_DIR + fn, data, 0666)
60 | if err != nil {
61 | log.Printf("Write file error: %v", err)
62 | sendErrorResponse(w, http.StatusInternalServerError, "Internal Error")
63 | return
64 | }
65 |
66 | w.WriteHeader(http.StatusCreated)
67 | io.WriteString(w, "Uploaded successfully")
68 | }
69 |
70 |
71 |
--------------------------------------------------------------------------------
/web/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "html/template"
5 | "net/http"
6 | "log"
7 | "github.com/julienschmidt/httprouter"
8 | "encoding/json"
9 | "io"
10 | "io/ioutil"
11 | "net/url"
12 | "net/http/httputil"
13 | )
14 |
15 | type HomePage struct {
16 | Name string
17 | }
18 |
19 | type UserPage struct {
20 | Name string
21 | }
22 |
23 | func homeHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
24 | cname, err1 := r.Cookie("username")
25 | sid, err2 := r.Cookie("session")
26 |
27 | if err1 != nil || err2 != nil{
28 | p:=&HomePage{Name: "avenssi"}
29 | t, e:=template.ParseFiles("./templates/home.html")
30 | if e!=nil {
31 | log.Printf("Parsing template home.html error: %s", e)
32 | return
33 | }
34 |
35 | t.Execute(w, p)
36 | return
37 | }
38 |
39 | if len(cname.Value) != 0 && len(sid.Value) != 0 {
40 | http.Redirect(w, r, "/userhome", http.StatusFound)
41 | return
42 | }
43 |
44 | }
45 |
46 | func userHomeHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
47 | cname, err1:=r.Cookie("username")
48 | _, err2 := r.Cookie("session")
49 | if err1!=nil || err2!=nil {
50 | http.Redirect(w, r, "/", http.StatusFound)
51 | return
52 | }
53 |
54 | fname:=r.FormValue("username")
55 | var p *UserPage
56 | if len(cname.Value) != 0{
57 | p=&UserPage{Name: cname.Value}
58 | } else if len(fname) != 0 {
59 | p=&UserPage{Name: fname}
60 | }
61 | t, e:=template.ParseFiles("./templates/userhome.html")
62 | if e!=nil{
63 | log.Printf("Parsing userhome.html error: %s", e)
64 | return
65 | }
66 | t.Execute(w, p)
67 | }
68 |
69 | func apiHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
70 | if r.Method!=http.MethodPost {
71 | re, _:=json.Marshal(ErrorRequestNotRecognized)
72 | io.WriteString(w, string(re))
73 | return
74 | }
75 |
76 | res, _:=ioutil.ReadAll(r.Body)
77 | apibody:=&ApiBody{}
78 | if err:=json.Unmarshal(res, apibody); err!=nil{
79 | re, _:=json.Marshal(ErrorRequestBodyParseFailed)
80 | io.WriteString(w, string(re))
81 | return
82 | }
83 | request(apibody, w, r)
84 | defer r.Body.Close()
85 | }
86 |
87 | func proxyHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
88 | u, _:=url.Parse("http://127.0.0.1:9000/")
89 | proxy := httputil.NewSingleHostReverseProxy(u)
90 | proxy.ServeHTTP(w, r)
91 | }
--------------------------------------------------------------------------------
/api/dbops/internal.go:
--------------------------------------------------------------------------------
1 | package dbops
2 |
3 | import (
4 | "database/sql"
5 | "log"
6 | "strconv"
7 | "sync"
8 | "video_server/api/defs"
9 | )
10 |
11 | func InsertSession(sid string, ttl int64, uname string) error {
12 | ttlstr := strconv.FormatInt(ttl, 10)
13 | stmtIns, err := dbConn.Prepare("INSERT INTO sessions (session_id, TTL, login_name) VALUES (?, ?, ?)")
14 | if err != nil {
15 | return err
16 | }
17 |
18 | _, err = stmtIns.Exec(sid, ttlstr, uname)
19 | if err != nil {
20 | return err
21 | }
22 |
23 | defer stmtIns.Close()
24 | return nil
25 | }
26 |
27 | func RetrieveSession(sid string) (*defs.SimpleSession, error) {
28 | ss := &defs.SimpleSession{}
29 | stmtOut, err := dbConn.Prepare("SELECT TTL, login_name FROM sessions WHERE session_id=?")
30 | if err != nil {
31 | return nil, err
32 | }
33 |
34 | var ttl string
35 | var uname string
36 | stmtOut.QueryRow(sid).Scan(&ttl, &uname)
37 | if err != nil && err != sql.ErrNoRows{
38 | return nil, err
39 | }
40 |
41 | if res, err := strconv.ParseInt(ttl, 10, 64); err == nil {
42 | ss.TTL = res
43 | ss.Username = uname
44 | } else {
45 | return nil, err
46 | }
47 |
48 | defer stmtOut.Close()
49 | return ss, nil
50 | }
51 |
52 | func RetrieveAllSessions() (*sync.Map, error) {
53 | m := &sync.Map{}
54 | stmtOut, err := dbConn.Prepare("SELECT * FROM sessions")
55 | if err != nil {
56 | log.Printf("%s", err)
57 | return nil, err
58 | }
59 |
60 | rows, err := stmtOut.Query()
61 | if err != nil {
62 | log.Printf("%s", err)
63 | return nil, err
64 | }
65 |
66 | for rows.Next() {
67 | var id string
68 | var ttlstr string
69 | var login_name string
70 | if er := rows.Scan(&id, &ttlstr, &login_name); er != nil {
71 | log.Printf("retrive sessions error: %s", er)
72 | break
73 | }
74 |
75 | if ttl, err1 := strconv.ParseInt(ttlstr, 10, 64); err1 == nil{
76 | ss := &defs.SimpleSession{Username: login_name, TTL: ttl}
77 | m.Store(id, ss)
78 | log.Printf(" session id: %s, ttl: %d", id, ss.TTL)
79 | }
80 |
81 |
82 | }
83 |
84 | return m, nil
85 | }
86 |
87 | func DeleteSession(sid string) error {
88 | stmtOut, err := dbConn.Prepare("DELETE FROM sessions WHERE session_id = ?")
89 | if err != nil {
90 | log.Printf("%s", err)
91 | return err
92 | }
93 |
94 | if _, err := stmtOut.Query(sid); err != nil {
95 | return err
96 | }
97 |
98 | return nil
99 | }
100 |
101 |
--------------------------------------------------------------------------------
/static/video-server-frontend/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/api/dbops/api_test.go:
--------------------------------------------------------------------------------
1 | package dbops
2 |
3 | import (
4 | "testing"
5 | "strconv"
6 | "time"
7 | "fmt"
8 | )
9 |
10 |
11 | var tempvid string
12 |
13 | func clearTables() {
14 | dbConn.Exec("truncate users")
15 | dbConn.Exec("truncate video_info")
16 | dbConn.Exec("truncate comments")
17 | dbConn.Exec("truncate sessions")
18 | }
19 |
20 | func TestMain(m *testing.M) {
21 | clearTables()
22 | m.Run()
23 | clearTables()
24 | }
25 |
26 | func TestUserWorkFlow(t *testing.T) {
27 | t.Run("Add", testAddUser)
28 | t.Run("Get", testGetUser)
29 | t.Run("Del", testDeleteUser)
30 | t.Run("Reget", testRegetUser)
31 | }
32 |
33 | func testAddUser(t *testing.T) {
34 | err := AddUserCredential("avenssi", "123")
35 | if err != nil {
36 | t.Errorf("Error of AddUser: %v", err)
37 | }
38 | }
39 |
40 | func testGetUser(t *testing.T) {
41 | pwd, err := GetUserCredential("avenssi")
42 | if pwd != "123" || err != nil {
43 | t.Errorf("Error of GetUser")
44 | }
45 | }
46 |
47 | func testDeleteUser(t *testing.T) {
48 | err := DeleteUser("avenssi", "123")
49 | if err != nil {
50 | t.Errorf("Error of DeleteUser: %v", err)
51 | }
52 | }
53 |
54 | func testRegetUser(t *testing.T) {
55 | pwd, err := GetUserCredential("avenssi")
56 | if err != nil {
57 | t.Errorf("Error of RegetUser: %v", err)
58 | }
59 |
60 | if pwd != "" {
61 | t.Errorf("Deleting user test failed")
62 | }
63 | }
64 |
65 | func TestVideoWorkFlow(t *testing.T) {
66 | clearTables()
67 | t.Run("PrepareUser", testAddUser)
68 | t.Run("AddVideo", testAddVideoInfo)
69 | t.Run("GetVideo", testGetVideoInfo)
70 | t.Run("DelVideo", testDeleteVideoInfo)
71 | t.Run("RegetVideo", testRegetVideoInfo)
72 | }
73 |
74 | func testAddVideoInfo(t *testing.T) {
75 | vi, err := AddNewVideo(1, "my-video")
76 | if err != nil {
77 | t.Errorf("Error of AddVideoInfo: %v", err)
78 | }
79 | tempvid = vi.Id
80 | }
81 |
82 | func testGetVideoInfo(t *testing.T) {
83 | _, err := GetVideoInfo(tempvid)
84 | if err != nil {
85 | t.Errorf("Error of GetVideoInfo: %v", err)
86 | }
87 | }
88 |
89 | func testDeleteVideoInfo(t *testing.T) {
90 | err := DeleteVideoInfo(tempvid)
91 | if err != nil {
92 | t.Errorf("Error of DeleteVideoInfo: %v", err)
93 | }
94 | }
95 |
96 | func testRegetVideoInfo(t *testing.T) {
97 | vi, err := GetVideoInfo(tempvid)
98 | if err != nil || vi != nil{
99 | t.Errorf("Error of RegetVideoInfo: %v", err)
100 | }
101 | }
102 |
103 | func TestComments(t *testing.T) {
104 | clearTables()
105 | t.Run("AddUser", testAddUser)
106 | t.Run("AddCommnets", testAddComments)
107 | t.Run("ListComments", testListComments)
108 | }
109 |
110 | func testAddComments(t *testing.T) {
111 | vid := "12345"
112 | aid := 1
113 | content := "I like this video"
114 |
115 | err := AddNewComments(vid, aid, content)
116 |
117 | if err != nil {
118 | t.Errorf("Error of AddComments: %v", err)
119 | }
120 | }
121 |
122 | func testListComments(t *testing.T) {
123 | vid := "12345"
124 | from := 1514764800
125 | to, _ := strconv.Atoi(strconv.FormatInt(time.Now().UnixNano()/1000000000, 10))
126 |
127 | res, err := ListComments(vid, from, to)
128 | if err != nil {
129 | t.Errorf("Error of ListComments: %v", err)
130 | }
131 |
132 | for i, ele := range res {
133 | fmt.Printf("comment: %d, %v \n", i, ele)
134 | }
135 | }
--------------------------------------------------------------------------------
/static/video-server-frontend/README.md:
--------------------------------------------------------------------------------
1 | noded# Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `yarn start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
13 |
14 | The page will reload if you make edits.\
15 | You will also see any lint errors in the console.
16 |
17 | ### `yarn test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `yarn build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `yarn eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
35 |
36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
39 |
40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `yarn build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/api/dbops/api.go:
--------------------------------------------------------------------------------
1 | package dbops
2 |
3 | import (
4 | "time"
5 | "log"
6 | "database/sql"
7 | _ "github.com/go-sql-driver/mysql"
8 | "video_server/api/defs"
9 | "video_server/api/utils"
10 | )
11 |
12 |
13 | func AddUserCredential(loginName string, pwd string) error {
14 | stmtIns, err := dbConn.Prepare("INSERT INTO users (login_name, pwd) VALUES (?, ?)")
15 | if err != nil {
16 | return err
17 | }
18 |
19 | _, err = stmtIns.Exec(loginName, pwd)
20 | if err != nil {
21 | return err
22 | }
23 |
24 | defer stmtIns.Close()
25 | return nil
26 | }
27 |
28 | func GetUserCredential(loginName string) (string, error) {
29 | stmtOut, err := dbConn.Prepare("SELECT pwd FROM users WHERE login_name = ?")
30 | if err != nil {
31 | log.Printf("%s", err)
32 | return "", err
33 | }
34 |
35 | var pwd string
36 | err = stmtOut.QueryRow(loginName).Scan(&pwd)
37 | if err != nil && err != sql.ErrNoRows {
38 | return "", err
39 | }
40 |
41 | defer stmtOut.Close()
42 |
43 | return pwd, nil
44 | }
45 |
46 | func DeleteUser(loginName string, pwd string) error {
47 | stmtDel, err := dbConn.Prepare("DELETE FROm users WHERE login_name=? AND pwd=?")
48 | if err != nil {
49 | log.Printf("DeleteUser error: %s", err)
50 | return err
51 | }
52 |
53 | _, err = stmtDel.Exec(loginName, pwd)
54 | if err != nil {
55 | return err
56 | }
57 |
58 | defer stmtDel.Close()
59 | return nil
60 | }
61 |
62 | func AddNewVideo(aid int, name string) (*defs.VideoInfo, error) {
63 | // create uuid
64 | vid, err := utils.NewUUID()
65 | if err != nil {
66 | return nil, err
67 | }
68 |
69 | t := time.Now()
70 | ctime := t.Format("Jan 02 2006, 15:04:05")
71 | stmtIns, err := dbConn.Prepare(`INSERT INTO video_info
72 | (id, author_id, name, display_ctime) VALUES(?, ?, ?, ?)`)
73 | if err != nil {
74 | return nil, err
75 | }
76 |
77 | _, err = stmtIns.Exec(vid, aid, name, ctime)
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | res := &defs.VideoInfo{Id: vid, AuthorId: aid, Name: name, DisplayCtime: ctime}
83 |
84 | defer stmtIns.Close()
85 | return res, nil
86 | }
87 |
88 |
89 | func GetVideoInfo(vid string) (*defs.VideoInfo, error) {
90 | stmtOut, err := dbConn.Prepare("SELECT author_id, name, display_ctime FROM video_info WHERE id=?")
91 |
92 | var aid int
93 | var dct string
94 | var name string
95 |
96 | err = stmtOut.QueryRow(vid).Scan(&aid, &name, &dct)
97 | if err != nil && err != sql.ErrNoRows{
98 | return nil, err
99 | }
100 |
101 | if err == sql.ErrNoRows {
102 | return nil, nil
103 | }
104 |
105 | defer stmtOut.Close()
106 |
107 | res := &defs.VideoInfo{Id: vid, AuthorId: aid, Name: name, DisplayCtime: dct}
108 |
109 | return res, nil
110 | }
111 |
112 | func DeleteVideoInfo(vid string) error {
113 | stmtDel, err := dbConn.Prepare("DELETE FROM video_info WHERE id=?")
114 | if err != nil {
115 | return err
116 | }
117 |
118 | _, err = stmtDel.Exec(vid)
119 | if err != nil {
120 | return err
121 | }
122 |
123 | defer stmtDel.Close()
124 | return nil
125 | }
126 |
127 | func AddNewComments(vid string, aid int, content string) error {
128 | id, err := utils.NewUUID()
129 | if err != nil {
130 | return err
131 | }
132 |
133 | stmtIns, err := dbConn.Prepare("INSERT INTO comments (id, video_id, author_id, content) values (?, ?, ?, ?)")
134 | if err != nil {
135 | return err
136 | }
137 |
138 | _, err = stmtIns.Exec(id, vid, aid, content)
139 | if err != nil {
140 | return err
141 | }
142 |
143 | defer stmtIns.Close()
144 | return nil
145 | }
146 |
147 | func ListComments(vid string, from, to int) ([]*defs.Comment, error) {
148 | stmtOut, err := dbConn.Prepare(` SELECT comments.id, users.Login_name, comments.content FROM comments
149 | INNER JOIN users ON comments.author_id = users.id
150 | WHERE comments.video_id = ? AND comments.time > FROM_UNIXTIME(?) AND comments.time <= FROM_UNIXTIME(?)`)
151 |
152 | var res []*defs.Comment
153 |
154 | rows, err := stmtOut.Query(vid, from, to)
155 | if err != nil {
156 | return res, err
157 | }
158 |
159 | for rows.Next() {
160 | var id, name, content string
161 | if err := rows.Scan(&id, &name, &content); err != nil {
162 | return res, err
163 | }
164 |
165 | c := &defs.Comment{Id: id, VideoId: vid, Author: name, Content: content}
166 | res = append(res, c)
167 | }
168 | defer stmtOut.Close()
169 |
170 | return res, nil
171 | }
172 |
--------------------------------------------------------------------------------
/static/templates/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
105 |
110 |
111 |
112 |
113 |
114 |
119 |
120 |
121 |
Welcome to MOVIEDASH
122 |
123 |
124 |
145 |
146 |
167 |
168 |
169 |
170 |
--------------------------------------------------------------------------------
/api/handlers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/julienschmidt/httprouter"
5 | "io"
6 | "io/ioutil"
7 | "log"
8 | "net/http"
9 | "video_server/api/dbops"
10 | "video_server/api/defs"
11 | "video_server/api/session"
12 | "encoding/json"
13 | )
14 |
15 | func CreateUser(w http.ResponseWriter, r *http.Request, p httprouter.Params){
16 | io.WriteString(w, "Create User Handler")
17 | }
18 |
19 | func Login(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
20 | res, _:=ioutil.ReadAll(r.Body)
21 | log.Printf("%s", res)
22 | ubody:=&defs.UserCredential{}
23 | if err:=json.Unmarshal(res, ubody); err!=nil {
24 | log.Printf("%s", err)
25 | //io.WriteString(w, "wrong")
26 | sendErrorResponse(w, defs.ErrorRequestBodyParseFailed)
27 | return
28 | }
29 | uname := p.ByName("username")
30 | log.Printf("Login url name: %s", uname)
31 | log.Printf("Login body name: %s", ubody.Username)
32 | if uname!=ubody.Username {
33 | sendErrorResponse(w, defs.ErrorNotAuthUser)
34 | return
35 | }
36 |
37 | log.Printf("%s", ubody.Username)
38 | pwd, err:=dbops.GetUserCredential(ubody.Username)
39 | log.Printf("Login pwd: %s", pwd)
40 | log.Printf("Login body pwd: %s", ubody.Pwd)
41 | if err!=nil || len(pwd)==0 || pwd!=ubody.Pwd {
42 | sendErrorResponse(w, defs.ErrorNotAuthUser)
43 | return
44 | }
45 | id:=session.GenerateNewSessionId(ubody.Username)
46 | si := &defs.SignedIn{Success: true, SessionId: id}
47 | if resp, err:=json.Marshal(si); err!= nil{
48 | sendErrorResponse(w, defs.ErrorInternalFaults)
49 | } else {
50 | sendNormalResponse(w, string(resp), 200)
51 | }
52 |
53 | //io.WriteString(w, uname)
54 |
55 | }
56 |
57 | func GetUserInfo(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
58 | if !ValidateUser(w, r) {
59 | log.Printf("Unauthorized user\n")
60 | return
61 | }
62 |
63 | uname:=p.ByName("username")
64 | u, err:=dbops.GetUser(uname)
65 | if err!=nil{
66 | log.Printf("Error in GetUserInfo: %s", err)
67 | sendErrorResponse(w, defs.ErrorDBError)
68 | return
69 | }
70 | ui:=&defs.UserInfo{Id: u.Id}
71 | if resp, err:=json.Marshal(ui); err!= nil {
72 | sendErrorResponse(w, defs.ErrorInternalFaults)
73 | } else {
74 | sendNormalResponse(w, string(resp), 200)
75 | }
76 | }
77 |
78 | func AddNewVideo(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
79 | if !ValidateUser(w, r) {
80 | log.Printf("Unathorized user \n")
81 | return
82 | }
83 |
84 | res, _ := ioutil.ReadAll(r.Body)
85 | nvbody := &defs.NewVideo{}
86 | if err := json.Unmarshal(res, nvbody); err != nil {
87 | log.Printf("%s", err)
88 | sendErrorResponse(w, defs.ErrorRequestBodyParseFailed)
89 | return
90 | }
91 |
92 | vi, err := dbops.AddNewVideo(nvbody.AuthorId, nvbody.Name)
93 | log.Printf("Author id : %d, name: %s \n", nvbody.AuthorId, nvbody.Name)
94 | if err != nil {
95 | log.Printf("Error in AddNewVideo: %s", err)
96 | sendErrorResponse(w, defs.ErrorDBError)
97 | return
98 | }
99 |
100 | if resp, err := json.Marshal(vi); err != nil {
101 | sendErrorResponse(w, defs.ErrorInternalFaults)
102 | } else {
103 | sendNormalResponse(w, string(resp), 201)
104 | }
105 |
106 | }
107 |
108 | func ListAllVideos(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
109 | if !ValidateUser(w, r) {
110 | return
111 | }
112 |
113 | uname := p.ByName("username")
114 | vs, err := dbops.ListVideoInfo(uname, 0, utils.GetCurrentTimestampSec())
115 | if err != nil {
116 | log.Printf("Error in ListAllvideos: %s", err)
117 | sendErrorResponse(w, defs.ErrorDBError)
118 | return
119 | }
120 |
121 | vsi := &defs.VideosInfo{Videos: vs}
122 | if resp, err := json.Marshal(vsi); err != nil {
123 | sendErrorResponse(w, defs.ErrorInternalFaults)
124 | } else {
125 | sendNormalResponse(w, string(resp), 200)
126 | }
127 |
128 | }
129 |
130 | func DeleteVideo(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
131 | if !ValidateUser(w, r) {
132 | return
133 | }
134 |
135 | vid := p.ByName("vid-id")
136 | err := dbops.DeleteVideoInfo(vid)
137 | if err != nil {
138 | log.Printf("Error in DeletVideo: %s", err)
139 | sendErrorResponse(w, defs.ErrorDBError)
140 | return
141 | }
142 |
143 | go utils.SendDeleteVideoRequest(vid)
144 | sendNormalResponse(w, "", 204)
145 | }
146 |
147 | func PostComment(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
148 | if !ValidateUser(w, r) {
149 | return
150 | }
151 |
152 | reqBody, _ := ioutil.ReadAll(r.Body)
153 |
154 | cbody := &defs.NewComment{}
155 | if err := json.Unmarshal(reqBody, cbody); err != nil {
156 | log.Printf("%s", err)
157 | sendErrorResponse(w, defs.ErrorRequestBodyParseFailed)
158 | return
159 | }
160 |
161 | vid := p.ByName("vid-id")
162 | if err := dbops.AddNewComments(vid, cbody.AuthorId, cbody.Content); err != nil {
163 | log.Printf("Error in PostComment: %s", err)
164 | sendErrorResponse(w, defs.ErrorDBError)
165 | } else {
166 | sendNormalResponse(w, "ok", 201)
167 | }
168 |
169 | }
170 |
171 | func ShowComments(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
172 | if !ValidateUser(w, r) {
173 | return
174 | }
175 |
176 | vid := p.ByName("vid-id")
177 | cm, err := dbops.ListComments(vid, 0, utils.GetCurrentTimestampSec())
178 | if err != nil {
179 | log.Printf("Error in ShowComments: %s", err)
180 | sendErrorResponse(w, defs.ErrorDBError)
181 | return
182 | }
183 |
184 | cms := &defs.Comments{Comments: cm}
185 | if resp, err := json.Marshal(cms); err != nil {
186 | sendErrorResponse(w, defs.ErrorInternalFaults)
187 | } else {
188 | sendNormalResponse(w, string(resp), 200)
189 | }
190 | }
--------------------------------------------------------------------------------
/static/templates/userhome.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
224 |
229 |
230 |
231 |
232 |
233 |
240 |
241 |
242 |
Welcome to MOVIEDASH
243 |
244 |
245 |
246 |
247 |
249 |
250 |
251 |
252 |
Comment submission succeeded
253 |
Error happened
254 |
255 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
276 |
277 |
282 |
283 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
--------------------------------------------------------------------------------
/static/templates/scripts/home.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | DEFAULT_COOKIE_EXPIRE_TIME = 30;
4 |
5 | uname = '';
6 | session = '';
7 | uid = 0;
8 | currentVideo = null;
9 | listedVideos = null;
10 |
11 | session = getCookie('session');
12 | uname = getCookie('username');
13 |
14 | initPage(function() {
15 | if (listedVideos !== null) {
16 | currentVideo = listedVideos[0];
17 | selectVideo(listedVideos[0]['id']);
18 | }
19 |
20 | $(".video-item").click(function() {
21 | var self = this.id
22 | listedVideos.forEach(function(item, index) {
23 | if (item['id'] === self) {
24 | currentVideo = item;
25 | return
26 | }
27 | });
28 |
29 | selectVideo(self);
30 | });
31 |
32 | $(".del-video-button").click(function() {
33 | var id = this.id.substring(4);
34 | deleteVideo(id, function(res, err) {
35 | if (err !== null) {
36 | //window.alert("encounter an error when try to delete video: " + id);
37 | popupErrorMsg("encounter an error when try to delete video: " + id);
38 | return;
39 | }
40 |
41 | popupNotificationMsg("Successfully deleted video: " + id)
42 | location.reload();
43 | });
44 | });
45 |
46 | $("#submit-comment").on('click', function() {
47 | var content = $("#comments-input").val();
48 | postComment(currentVideo['id'], content, function(res, err) {
49 | if (err !== null) {
50 | popupErrorMsg("encounter and error when try to post a comment: " + content);
51 | return;
52 | }
53 |
54 | if (res === "ok") {
55 | popupNotificationMsg("New comment posted")
56 | $("#comments-input").val("");
57 |
58 | refreshComments(currentVideo['id']);
59 | }
60 | });
61 | });
62 | });
63 |
64 | // home page event registry
65 | $("#regbtn").on('click', function(e) {
66 | $("#regbtn").text('Loading...')
67 | e.preventDefault()
68 | registerUser(function(res, err) {
69 | if (err != null) {
70 | $('#regbtn').text("Register")
71 | popupErrorMsg('encounter an error, pls check your username or pwd');
72 | return;
73 | }
74 |
75 | var obj = JSON.parse(res);
76 | setCookie("session", obj["session_id"], DEFAULT_COOKIE_EXPIRE_TIME);
77 | setCookie("username", uname, DEFAULT_COOKIE_EXPIRE_TIME);
78 | $("#regsubmit").submit();
79 | });
80 | });
81 |
82 | $("#siginbtn").on('click', function(e) {
83 |
84 | $("#siginbtn").text('Loading...')
85 | e.preventDefault();
86 | signinUser(function(res, err) {
87 | if (err != null) {
88 | $('#siginbtn').text("Sign In");
89 | //window.alert('encounter an error, pls check your username or pwd')
90 | popupErrorMsg('encounter an error, pls check your username or pwd');
91 | return;
92 | }
93 |
94 | var obj = JSON.parse(res);
95 | setCookie("session", obj["session_id"], DEFAULT_COOKIE_EXPIRE_TIME);
96 | setCookie("username", uname, DEFAULT_COOKIE_EXPIRE_TIME);
97 | $("#siginsubmit").submit();
98 | });
99 | });
100 |
101 | $("#signinhref").on('click', function() {
102 | $("#regsubmit").hide();
103 | $("#siginsubmit").show();
104 | });
105 |
106 | $("#registerhref").on('click', function() {
107 | $("#regsubmit").show();
108 | $("#siginsubmit").hide();
109 | });
110 |
111 | // userhome event register
112 | $("#upload").on('click', function() {
113 | $("#uploadvideomodal").show();
114 |
115 | });
116 |
117 |
118 | $("#uploadform").on('submit', function(e) {
119 | e.preventDefault()
120 | var vname = $('#vname').val();
121 |
122 | createVideo(vname, function(res, err) {
123 | if (err != null ) {
124 | //window.alert('encounter an error when try to create video');
125 | popupErrorMsg('encounter an error when try to create video');
126 | return;
127 | }
128 |
129 | var obj = JSON.parse(res);
130 | var formData = new FormData();
131 | formData.append('file', $('#inputFile')[0].files[0]);
132 |
133 | $.ajax({
134 | url : 'http://' + window.location.hostname + ':8080/upload/' + obj['id'],
135 | //url:'http://127.0.0.1:8080/upload/dbibi',
136 | type : 'POST',
137 | data : formData,
138 | //headers: {'Access-Control-Allow-Origin': 'http://127.0.0.1:9000'},
139 | crossDomain: true,
140 | processData: false, // tell jQuery not to process the data
141 | contentType: false, // tell jQuery not to set contentType
142 | success : function(data) {
143 | console.log(data);
144 | $('#uploadvideomodal').hide();
145 | location.reload();
146 | //window.alert("hoa");
147 | },
148 | complete: function(xhr, textStatus) {
149 | if (xhr.status === 204) {
150 | window.alert("finish")
151 | return;
152 | }
153 | if (xhr.status === 400) {
154 | $("#uploadvideomodal").hide();
155 | popupErrorMsg('file is too big');
156 | return;
157 | }
158 | }
159 |
160 | });
161 | });
162 | });
163 |
164 | $(".close").on('click', function() {
165 | $("#uploadvideomodal").hide();
166 | });
167 |
168 | $("#logout").on('click', function() {
169 | setCookie("session", "", -1)
170 | setCookie("username", "", -1)
171 | });
172 |
173 |
174 | $(".video-item").click(function () {
175 | var url = 'http://' + window.location.hostname + ':9000/videos/'+ this.id
176 | var video = $("#curr-video");
177 | video[0].attr('src', url);
178 | video.load();
179 | });
180 | });
181 |
182 | function initPage(callback) {
183 | getUserId(function(res, err) {
184 | if (err != null) {
185 | window.alert("Encountered error when loading user id");
186 | return;
187 | }
188 |
189 | var obj = JSON.parse(res);
190 | uid = obj['id'];
191 | //window.alert(obj['id']);
192 | listAllVideos(function(res, err) {
193 | if (err != null) {
194 | //window.alert('encounter an error, pls check your username or pwd');
195 | popupErrorMsg('encounter an error, pls check your username or pwd');
196 | return;
197 | }
198 | var obj = JSON.parse(res);
199 | listedVideos = obj['videos'];
200 | obj['videos'].forEach(function(item, index) {
201 | var ele = htmlVideoListElement(item['id'], item['name'], item['display_ctime']);
202 | $("#items").append(ele);
203 | });
204 | callback();
205 | });
206 | });
207 | }
208 |
209 | function setCookie(cname, cvalue, exmin) {
210 | var d = new Date();
211 | d.setTime(d.getTime() + (exmin * 60 * 1000));
212 | var expires = "expires="+d.toUTCString();
213 | document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
214 | }
215 |
216 | function getCookie(cname) {
217 | var name = cname + "=";
218 | var ca = document.cookie.split(';');
219 | for(var i = 0; i < ca.length; i++) {
220 | var c = ca[i];
221 | while (c.charAt(0) == ' ') {
222 | c = c.substring(1);
223 | }
224 | if (c.indexOf(name) == 0) {
225 | return c.substring(name.length, c.length);
226 | }
227 | }
228 | return "";
229 | }
230 |
231 | // DOM operations
232 | function selectVideo(vid) {
233 | var url = 'http://' + window.location.hostname + ':8080/videos/'+ vid
234 | var video = $("#curr-video");
235 | $("#curr-video:first-child").attr('src', url);
236 | $("#curr-video-name").text(currentVideo['name']);
237 | $("#curr-video-ctime").text('Uploaded at: ' + currentVideo['display_ctime']);
238 | //currentVideoId = vid;
239 | refreshComments(vid);
240 | }
241 |
242 | function refreshComments(vid) {
243 | listAllComments(vid, function (res, err) {
244 | if (err !== null) {
245 | //window.alert("encounter an error when loading comments");
246 | popupErrorMsg('encounter an error when loading comments');
247 | return
248 | }
249 |
250 | var obj = JSON.parse(res);
251 | $("#comments-history").empty();
252 | if (obj['comments'] === null) {
253 | $("#comments-total").text('0 Comments');
254 | } else {
255 | $("#comments-total").text(obj['comments'].length + ' Comments');
256 | }
257 | obj['comments'].forEach(function(item, index) {
258 | var ele = htmlCommentListElement(item['id'], item['author'], item['content']);
259 | $("#comments-history").append(ele);
260 | });
261 |
262 | });
263 | }
264 |
265 | function popupNotificationMsg(msg) {
266 | var x = document.getElementById("snackbar");
267 | $("#snackbar").text(msg);
268 | x.className = "show";
269 | setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2000);
270 | }
271 |
272 | function popupErrorMsg(msg) {
273 | var x = document.getElementById("errorbar");
274 | $("#errorbar").text(msg);
275 | x.className = "show";
276 | setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2000);
277 | }
278 |
279 | function htmlCommentListElement(cid, author, content) {
280 | var ele = $('', {
281 | id: cid
282 | });
283 |
284 | ele.append(
285 | $('', {
286 | class: 'comment-author',
287 | text: author + ' says:'
288 | })
289 | );
290 | ele.append(
291 | $('', {
292 | class: 'comment',
293 | text: content
294 | })
295 | );
296 |
297 | ele.append('
');
298 |
299 | return ele;
300 | }
301 |
302 | function htmlVideoListElement(vid, name, ctime) {
303 | var ele = $('', {
304 | href: '#'
305 | });
306 | ele.append(
307 | $('', {
308 | width:'320',
309 | height:'240',
310 | poster:'/statics/img/preloader.jpg',
311 | controls: true
312 | //href: '#'
313 | })
314 | );
315 | ele.append(
316 | $('', {
317 | text: name
318 | })
319 | );
320 | ele.append(
321 | $('', {
322 | text: ctime
323 | })
324 | );
325 |
326 |
327 | var res = $('', {
328 | id: vid,
329 | class: 'video-item'
330 | }).append(ele);
331 |
332 | res.append(
333 | $('', {
334 | id: 'del-' + vid,
335 | type: 'button',
336 | class: 'del-video-button',
337 | text: 'Delete'
338 | })
339 | );
340 |
341 | res.append(
342 | $('
', {
343 | size: '2'
344 | }).css('border-color', 'grey')
345 | );
346 |
347 | return res;
348 | }
349 |
350 | // Async ajax methods
351 |
352 | // User operations
353 | function registerUser(callback) {
354 | var username = $("#username").val();
355 | var pwd = $("#pwd").val();
356 | var apiUrl = window.location.hostname + ':8080/api';
357 |
358 | if (username == '' || pwd == '') {
359 | callback(null, err);
360 | }
361 |
362 | var reqBody = {
363 | 'user_name': username,
364 | 'pwd': pwd
365 | }
366 |
367 | var dat = {
368 | 'url': 'http://'+ window.location.hostname + ':8000/user',
369 | 'method': 'POST',
370 | 'req_body': JSON.stringify(reqBody)
371 | };
372 |
373 |
374 |
375 |
376 | $.ajax({
377 | url : 'http://' + window.location.hostname + ':8080/api',
378 | type : 'post',
379 | data : JSON.stringify(dat),
380 | statusCode: {
381 | 500: function() {
382 | callback(null, "internal error");
383 | }
384 | },
385 | complete: function(xhr, textStatus) {
386 | if (xhr.status >= 400) {
387 | callback(null, "Error of Signin");
388 | return;
389 | }
390 | }
391 | }).done(function(data, statusText, xhr){
392 | if (xhr.status >= 400) {
393 | callback(null, "Error of register");
394 | return;
395 | }
396 |
397 | uname = username;
398 | callback(data, null);
399 | });
400 | }
401 |
402 | function signinUser(callback) {
403 | var username = $("#susername").val();
404 | var pwd = $("#spwd").val();
405 | var apiUrl = window.location.hostname + ':8080/api';
406 |
407 | if (username == '' || pwd == '') {
408 | callback(null, err);
409 | }
410 |
411 | var reqBody = {
412 | 'user_name': username,
413 | 'pwd': pwd
414 | }
415 |
416 | var dat = {
417 | 'url': 'http://'+ window.location.hostname + ':8000/user/' + username,
418 | 'method': 'POST',
419 | 'req_body': JSON.stringify(reqBody)
420 | };
421 |
422 | $.ajax({
423 | url : 'http://' + window.location.hostname + ':8080/api',
424 | type : 'post',
425 | data : JSON.stringify(dat),
426 | statusCode: {
427 | 500: function() {
428 | callback(null, "Internal error");
429 | }
430 | },
431 | complete: function(xhr, textStatus) {
432 | if (xhr.status >= 400) {
433 | callback(null, "Error of Signin");
434 | return;
435 | }
436 | }
437 | }).done(function(data, statusText, xhr){
438 | if (xhr.status >= 400) {
439 | callback(null, "Error of Signin");
440 | return;
441 | }
442 | uname = username;
443 |
444 | callback(data, null);
445 | });
446 | }
447 |
448 | function getUserId(callback) {
449 | var dat = {
450 | 'url': 'http://' + window.location.hostname + ':8000/user/' + uname,
451 | 'method': 'GET'
452 | };
453 |
454 | $.ajax({
455 | url: 'http://' + window.location.hostname + ':8080/api',
456 | type: 'post',
457 | data: JSON.stringify(dat),
458 | headers: {'X-Session-Id': session},
459 | statusCode: {
460 | 500: function() {
461 | callback(null, "Internal Error");
462 | }
463 | },
464 | complete: function(xhr, textStatus) {
465 | if (xhr.status >= 400) {
466 | callback(null, "Error of getUserId");
467 | return;
468 | }
469 | }
470 | }).done(function (data, statusText, xhr) {
471 | callback(data, null);
472 | });
473 | }
474 |
475 | // Video operations
476 | function createVideo(vname, callback) {
477 | var reqBody = {
478 | 'author_id': uid,
479 | 'name': vname
480 | };
481 |
482 | var dat = {
483 | 'url': 'http://' + window.location.hostname + ':8000/user/' + uname + '/videos',
484 | 'method': 'POST',
485 | 'req_body': JSON.stringify(reqBody)
486 | };
487 |
488 | $.ajax({
489 | url : 'http://' + window.location.hostname + ':8080/api',
490 | type : 'post',
491 | data : JSON.stringify(dat),
492 | headers: {'X-Session-Id': session},
493 | statusCode: {
494 | 500: function() {
495 | callback(null, "Internal error");
496 | }
497 | },
498 | complete: function(xhr, textStatus) {
499 | if (xhr.status >= 400) {
500 | callback(null, "Error of Signin");
501 | return;
502 | }
503 | }
504 | }).done(function(data, statusText, xhr){
505 | if (xhr.status >= 400) {
506 | callback(null, "Error of Signin");
507 | return;
508 | }
509 | callback(data, null);
510 | });
511 | }
512 |
513 | function listAllVideos(callback) {
514 | var dat = {
515 | 'url': 'http://' + window.location.hostname + ':8000/user/' + uname + '/videos',
516 | 'method': 'GET',
517 | 'req_body': ''
518 | };
519 |
520 | $.ajax({
521 | url : 'http://' + window.location.hostname + ':8080/api',
522 | type : 'post',
523 | data : JSON.stringify(dat),
524 | headers: {'X-Session-Id': session},
525 | statusCode: {
526 | 500: function() {
527 | callback(null, "Internal error");
528 | }
529 | },
530 | complete: function(xhr, textStatus) {
531 | if (xhr.status >= 400) {
532 | callback(null, "Error of Signin");
533 | return;
534 | }
535 | }
536 | }).done(function(data, statusText, xhr){
537 | if (xhr.status >= 400) {
538 | callback(null, "Error of Signin");
539 | return;
540 | }
541 | callback(data, null);
542 | });
543 | }
544 |
545 | function deleteVideo(vid, callback) {
546 | var dat = {
547 | 'url': 'http://' + window.location.hostname + ':8000/user/' + uname + '/videos/' + vid,
548 | 'method': 'DELETE',
549 | 'req_body': ''
550 | };
551 |
552 | $.ajax({
553 | url : 'http://' + window.location.hostname + ':8080/api',
554 | type : 'post',
555 | data : JSON.stringify(dat),
556 | headers: {'X-Session-Id': session},
557 | statusCode: {
558 | 500: function() {
559 | callback(null, "Internal error");
560 | }
561 | },
562 | complete: function(xhr, textStatus) {
563 | if (xhr.status >= 400) {
564 | callback(null, "Error of Signin");
565 | return;
566 | }
567 | }
568 | }).done(function(data, statusText, xhr){
569 | if (xhr.status >= 400) {
570 | callback(null, "Error of Signin");
571 | return;
572 | }
573 | callback(data, null);
574 | });
575 | }
576 |
577 | // Comments operations
578 | function postComment(vid, content, callback) {
579 | var reqBody = {
580 | 'author_id': uid,
581 | 'content': content
582 | }
583 |
584 |
585 | var dat = {
586 | 'url': 'http://' + window.location.hostname + ':8000/videos/' + vid + '/comments',
587 | 'method': 'POST',
588 | 'req_body': JSON.stringify(reqBody)
589 | };
590 |
591 | $.ajax({
592 | url : 'http://' + window.location.hostname + ':8080/api',
593 | type : 'post',
594 | data : JSON.stringify(dat),
595 | headers: {'X-Session-Id': session},
596 | statusCode: {
597 | 500: function() {
598 | callback(null, "Internal error");
599 | }
600 | },
601 | complete: function(xhr, textStatus) {
602 | if (xhr.status >= 400) {
603 | callback(null, "Error of Signin");
604 | return;
605 | }
606 | }
607 | }).done(function(data, statusText, xhr){
608 | if (xhr.status >= 400) {
609 | callback(null, "Error of Signin");
610 | return;
611 | }
612 | callback(data, null);
613 | });
614 | }
615 |
616 | function listAllComments(vid, callback) {
617 | var dat = {
618 | 'url': 'http://' + window.location.hostname + ':8000/videos/' + vid + '/comments',
619 | 'method': 'GET',
620 | 'req_body': ''
621 | };
622 |
623 | $.ajax({
624 | url : 'http://' + window.location.hostname + ':8080/api',
625 | type : 'post',
626 | data : JSON.stringify(dat),
627 | headers: {'X-Session-Id': session},
628 | statusCode: {
629 | 500: function() {
630 | callback(null, "Internal error");
631 | }
632 | },
633 | complete: function(xhr, textStatus) {
634 | if (xhr.status >= 400) {
635 | callback(null, "Error of Signin");
636 | return;
637 | }
638 | }
639 | }).done(function(data, statusText, xhr){
640 | if (xhr.status >= 400) {
641 | callback(null, "Error of Signin");
642 | return;
643 | }
644 | callback(data, null);
645 | });
646 | }
647 |
648 |
649 |
650 |
651 |
652 |
653 |
--------------------------------------------------------------------------------
259 |