first commit

This commit is contained in:
kenneth
2023-11-28 09:50:42 +00:00
parent d940668996
commit 5a01af92af
33 changed files with 1939 additions and 0 deletions

20
internal/handlers/home.go Normal file
View 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
}
}

View 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)
}
}

View 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
View 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
View 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)
}

View 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)
*/