first commit
This commit is contained in:
20
internal/handlers/home.go
Normal file
20
internal/handlers/home.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (server *Server) home(w http.ResponseWriter, r *http.Request) {
|
||||
t, err := template.ParseFiles("web/templates/home.html.tmpl")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = t.Execute(w, nil)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
50
internal/handlers/response.go
Normal file
50
internal/handlers/response.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type response struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func Respond(w http.ResponseWriter, message string, v any, statusCode int) {
|
||||
rsp := response{
|
||||
Success: true,
|
||||
Message: message,
|
||||
Data: v,
|
||||
}
|
||||
b, err := json.Marshal(rsp)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(statusCode)
|
||||
_, err = w.Write(b)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
log.Printf("could not write http response: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func RespondJson(w http.ResponseWriter, v any) {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = w.Write(b)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
log.Printf("could not write http response: %v\n", err)
|
||||
}
|
||||
}
|
||||
97
internal/handlers/server.go
Normal file
97
internal/handlers/server.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/zhang2092/mediahls/internal/db"
|
||||
"github.com/zhang2092/mediahls/internal/pkg/config"
|
||||
"github.com/zhang2092/mediahls/internal/pkg/logger"
|
||||
"github.com/zhang2092/mediahls/internal/pkg/token"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
conf *config.Config
|
||||
router *mux.Router
|
||||
|
||||
store db.Store
|
||||
tokenMaker token.Maker
|
||||
}
|
||||
|
||||
func NewServer(conf *config.Config, store db.Store) (*Server, error) {
|
||||
tokenMaker, err := token.NewPasetoMaker(conf.TokenSymmetricKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create token maker: %w", err)
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
conf: conf,
|
||||
store: store,
|
||||
tokenMaker: tokenMaker,
|
||||
}
|
||||
|
||||
server.setupRouter()
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (server *Server) setupRouter() {
|
||||
router := mux.NewRouter()
|
||||
router.PathPrefix("/statics/").Handler(http.StripPrefix("/statics/", http.FileServer(http.Dir("web/statics"))))
|
||||
|
||||
router.HandleFunc("/register", server.registerView).Methods(http.MethodGet)
|
||||
router.HandleFunc("/register", server.register).Methods(http.MethodPost)
|
||||
router.HandleFunc("/login", server.loginView).Methods(http.MethodGet)
|
||||
router.HandleFunc("/login", server.login).Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/", server.home).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/play/{xid}", server.play).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/upload", server.upload).Methods(http.MethodGet, http.MethodPost)
|
||||
|
||||
server.router = router
|
||||
}
|
||||
|
||||
func (server *Server) Start(db *sql.DB) {
|
||||
srv := &http.Server{
|
||||
Addr: server.conf.ServerAddress,
|
||||
Handler: server.router,
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Printf("server start on: %s\n", server.conf.ServerAddress)
|
||||
if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("listen: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Println("Shutting down server...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := db.Close(); err != nil {
|
||||
log.Fatal("Server db to shutdown:", err)
|
||||
}
|
||||
if err := logger.Logger.Sync(); err != nil {
|
||||
log.Fatal("Server log sync:", err)
|
||||
}
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Fatal("Server forced to shutdown:", err)
|
||||
}
|
||||
|
||||
log.Println("Server exiting")
|
||||
}
|
||||
102
internal/handlers/upload.go
Normal file
102
internal/handlers/upload.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
nanoId "github.com/matoous/go-nanoid"
|
||||
"github.com/zhang2092/mediahls/internal/pkg/fileutil"
|
||||
)
|
||||
|
||||
func (server *Server) upload(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
defer r.Body.Close()
|
||||
|
||||
file, fileHeader, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, err = w.Write([]byte(err.Error()))
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buff := make([]byte, 512)
|
||||
_, err = file.Read(buff)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// filetype := http.DetectContentType(buff)
|
||||
// if filetype != "image/jpeg" && filetype != "image/png" {
|
||||
// http.Error(w, "The provided file format is not allowed. Please upload a JPEG or PNG image", http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
dir := path.Join("upload", time.Now().Format("20060102"))
|
||||
exist, _ := fileutil.PathExists(dir)
|
||||
if !exist {
|
||||
err := os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filename, err := nanoId.Nanoid()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
filePath := path.Join("", dir, filename+filepath.Ext(fileHeader.Filename))
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, file)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err = w.Write([]byte(filePath))
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t, err := template.ParseFiles("web/templates/upload.html.tmpl")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = t.Execute(w, nil)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
146
internal/handlers/user.go
Normal file
146
internal/handlers/user.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/zhang2092/mediahls/internal/db"
|
||||
pwd "github.com/zhang2092/mediahls/internal/pkg/password"
|
||||
)
|
||||
|
||||
func (server *Server) registerView(w http.ResponseWriter, r *http.Request) {
|
||||
t, err := template.ParseFiles("web/templates/register.html.tmpl")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = t.Execute(w, nil)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type userResponse struct {
|
||||
Username string `json:"username"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
PasswordChangedAt time.Time `json:"password_changed_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func newUserResponse(user db.User) userResponse {
|
||||
return userResponse{
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
CreatedAt: user.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) register(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
username := r.PostFormValue("username")
|
||||
email := r.PostFormValue("email")
|
||||
password := r.PostFormValue("password")
|
||||
|
||||
hashedPassword, err := pwd.BcryptHashPassword(password)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
arg := db.CreateUserParams{
|
||||
Username: username,
|
||||
HashedPassword: hashedPassword,
|
||||
Email: email,
|
||||
}
|
||||
|
||||
user, err := server.store.CreateUser(r.Context(), arg)
|
||||
if err != nil {
|
||||
if server.store.IsUniqueViolation(err) {
|
||||
http.Error(w, "数据已经存在", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rsp := newUserResponse(user)
|
||||
Respond(w, "ok", rsp, http.StatusOK)
|
||||
}
|
||||
|
||||
func (server *Server) loginView(w http.ResponseWriter, r *http.Request) {
|
||||
t, err := template.ParseFiles("web/templates/login.html.tmpl")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = t.Execute(w, nil)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type loginUserResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`
|
||||
User userResponse `json:"user"`
|
||||
}
|
||||
|
||||
func (server *Server) login(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
username := r.PostFormValue("username")
|
||||
password := r.PostFormValue("password")
|
||||
ctx := r.Context()
|
||||
|
||||
user, err := server.store.GetUserByName(ctx, username)
|
||||
if err != nil {
|
||||
if server.store.IsNoRows(sql.ErrNoRows) {
|
||||
http.Error(w, "用户不存在", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = pwd.BcryptComparePassword(password, user.HashedPassword)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
accessToken, accessPayload, err := server.tokenMaker.CreateToken(
|
||||
user.ID,
|
||||
user.Username,
|
||||
server.conf.AccessTokenDuration,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rsp := loginUserResponse{
|
||||
AccessToken: accessToken,
|
||||
AccessTokenExpiresAt: accessPayload.ExpiresAt.Time,
|
||||
User: newUserResponse(user),
|
||||
}
|
||||
Respond(w, "ok", rsp, http.StatusOK)
|
||||
}
|
||||
20
internal/handlers/video.go
Normal file
20
internal/handlers/video.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package handlers
|
||||
|
||||
import "net/http"
|
||||
|
||||
func (server *Server) play(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
// 直接播放mp4
|
||||
video, err := os.Open("web/statics/git.mp4")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("failed to open file"))
|
||||
return
|
||||
}
|
||||
defer video.Close()
|
||||
|
||||
http.ServeContent(w, r, "git", time.Now(), video)
|
||||
*/
|
||||
Reference in New Issue
Block a user