current user transfer html use template func
This commit is contained in:
parent
26ee393549
commit
a6338a36ea
@ -4,15 +4,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
"github.com/zhang2092/mediahls/internal/pkg/cookie"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AuthorizeCookie = "authorize"
|
AuthorizeCookie = "authorize"
|
||||||
ContextUser CtxTypeUser = "context_user"
|
ContextUser ctxKey = "context_user"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CtxTypeUser string
|
type ctxKey string
|
||||||
|
|
||||||
type Authorize struct {
|
type Authorize struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
@ -25,13 +24,10 @@ func genId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) isRedirect(w http.ResponseWriter, r *http.Request) {
|
func (server *Server) isRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
_, err := server.withCookie(r)
|
u := withUser(r.Context())
|
||||||
if err != nil {
|
if u != nil {
|
||||||
// 1. 删除cookie
|
// 已经登录, 直接到首页
|
||||||
cookie.DeleteCookie(w, cookie.AuthorizeName)
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// cookie 校验成功
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,25 +8,12 @@ import (
|
|||||||
"github.com/zhang2092/mediahls/internal/db"
|
"github.com/zhang2092/mediahls/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// obj
|
|
||||||
|
|
||||||
// homePageData 首页数据
|
|
||||||
type homePageData struct {
|
|
||||||
Authorize
|
|
||||||
Videos []db.Video
|
|
||||||
}
|
|
||||||
|
|
||||||
// view
|
// view
|
||||||
|
|
||||||
// home 首页
|
// home 首页
|
||||||
func (server *Server) homeView(w http.ResponseWriter, r *http.Request) {
|
func (server *Server) homeView(w http.ResponseWriter, r *http.Request) {
|
||||||
data := homePageData{}
|
|
||||||
auth, err := server.withCookie(r)
|
|
||||||
if err == nil {
|
|
||||||
data.Authorize = *auth
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
var result []db.Video
|
||||||
videos, err := server.store.ListVideos(ctx, db.ListVideosParams{
|
videos, err := server.store.ListVideos(ctx, db.ListVideosParams{
|
||||||
Limit: 100,
|
Limit: 100,
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
@ -38,9 +25,9 @@ func (server *Server) homeView(w http.ResponseWriter, r *http.Request) {
|
|||||||
item.Description = temp
|
item.Description = temp
|
||||||
log.Println(item.Description)
|
log.Println(item.Description)
|
||||||
}
|
}
|
||||||
data.Videos = append(data.Videos, item)
|
result = append(result, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server.renderLayout(w, r, data, "home.html.tmpl")
|
server.renderLayout(w, r, result, "home.html.tmpl")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,57 +2,47 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/zhang2092/mediahls/internal/pkg/convert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (server *Server) authorizeMiddleware(next http.Handler) http.Handler {
|
func (server *Server) authorize(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
u, err := server.withCookie(r)
|
u := withUser(r.Context())
|
||||||
if err != nil {
|
if u == nil {
|
||||||
http.Redirect(w, r, "/login", http.StatusFound)
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(u)
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (server *Server) setUser(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cookie, err := r.Cookie(AuthorizeCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("json marshal authorize user: %v", err)
|
next.ServeHTTP(w, r)
|
||||||
http.Redirect(w, r, "/login", http.StatusFound)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u := Authorize{}
|
||||||
|
err = server.secureCookie.Decode(AuthorizeCookie, cookie.Value, &u)
|
||||||
|
if err != nil {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
ctx = context.WithValue(ctx, ContextUser, b)
|
ctx = context.WithValue(ctx, ContextUser, u)
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) withCookie(r *http.Request) (*Authorize, error) {
|
func withUser(ctx context.Context) *Authorize {
|
||||||
cookie, err := r.Cookie(AuthorizeCookie)
|
val := ctx.Value(ContextUser)
|
||||||
if err != nil {
|
if u, ok := val.(Authorize); ok {
|
||||||
return nil, err
|
return &u
|
||||||
}
|
}
|
||||||
|
|
||||||
u := &Authorize{}
|
return nil
|
||||||
err = server.secureCookie.Decode(AuthorizeCookie, cookie.Value, u)
|
|
||||||
if err != nil {
|
|
||||||
// log.Printf("secure decode cookie: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func withUser(ctx context.Context) Authorize {
|
|
||||||
var result Authorize
|
|
||||||
ctxValue, err := convert.ToByteE(ctx.Value(ContextUser))
|
|
||||||
if err != nil {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
json.Unmarshal(ctxValue, &result)
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,9 @@ func (server *Server) renderLayout(w http.ResponseWriter, r *http.Request, data
|
|||||||
"csrfField": func() template.HTML {
|
"csrfField": func() template.HTML {
|
||||||
return csrf.TemplateField(r)
|
return csrf.TemplateField(r)
|
||||||
},
|
},
|
||||||
|
"currentUser": func() *Authorize {
|
||||||
|
return withUser(r.Context())
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
tpl := template.Must(t.Clone())
|
tpl := template.Must(t.Clone())
|
||||||
|
|||||||
@ -75,6 +75,7 @@ func (server *Server) setupRouter() {
|
|||||||
csrf.CookieName("authorize_csrf"),
|
csrf.CookieName("authorize_csrf"),
|
||||||
)
|
)
|
||||||
router.Use(csrfMiddleware)
|
router.Use(csrfMiddleware)
|
||||||
|
router.Use(server.setUser)
|
||||||
|
|
||||||
router.Handle("/register", hds.MethodHandler{
|
router.Handle("/register", hds.MethodHandler{
|
||||||
http.MethodGet: http.HandlerFunc(server.registerView),
|
http.MethodGet: http.HandlerFunc(server.registerView),
|
||||||
@ -93,7 +94,7 @@ func (server *Server) setupRouter() {
|
|||||||
router.HandleFunc("/media/{xid}/stream/{segName:index[0-9]+.ts}", server.stream).Methods(http.MethodGet)
|
router.HandleFunc("/media/{xid}/stream/{segName:index[0-9]+.ts}", server.stream).Methods(http.MethodGet)
|
||||||
|
|
||||||
subRouter := router.PathPrefix("/").Subrouter()
|
subRouter := router.PathPrefix("/").Subrouter()
|
||||||
subRouter.Use(server.authorizeMiddleware)
|
subRouter.Use(server.authorize)
|
||||||
|
|
||||||
subRouter.HandleFunc("/me/videos", server.videosView).Methods(http.MethodGet)
|
subRouter.HandleFunc("/me/videos", server.videosView).Methods(http.MethodGet)
|
||||||
subRouter.HandleFunc("/me/videos/p{page}", server.videosView).Methods(http.MethodGet)
|
subRouter.HandleFunc("/me/videos/p{page}", server.videosView).Methods(http.MethodGet)
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
// registerPageData 注册页面数据
|
// registerPageData 注册页面数据
|
||||||
type registerPageData struct {
|
type registerPageData struct {
|
||||||
Authorize
|
|
||||||
Summary string
|
Summary string
|
||||||
Email string
|
Email string
|
||||||
EmailMsg string
|
EmailMsg string
|
||||||
@ -26,7 +25,6 @@ type registerPageData struct {
|
|||||||
|
|
||||||
// loginPageData 登录页面数据
|
// loginPageData 登录页面数据
|
||||||
type loginPageData struct {
|
type loginPageData struct {
|
||||||
Authorize
|
|
||||||
Summary string
|
Summary string
|
||||||
Email string
|
Email string
|
||||||
EmailMsg string
|
EmailMsg string
|
||||||
|
|||||||
@ -18,21 +18,8 @@ import (
|
|||||||
|
|
||||||
// obj
|
// obj
|
||||||
|
|
||||||
// videoPageData 播放页面数据
|
|
||||||
type videoPageData struct {
|
|
||||||
Authorize
|
|
||||||
Video db.Video
|
|
||||||
}
|
|
||||||
|
|
||||||
// videosPageData 视频列表数据
|
|
||||||
type videosPageData struct {
|
|
||||||
Authorize
|
|
||||||
Videos []db.Video
|
|
||||||
}
|
|
||||||
|
|
||||||
// videoEditPageData 视频编辑数据
|
// videoEditPageData 视频编辑数据
|
||||||
type videoEditPageData struct {
|
type videoEditPageData struct {
|
||||||
Authorize
|
|
||||||
Summary string
|
Summary string
|
||||||
ID string
|
ID string
|
||||||
IDMsg string
|
IDMsg string
|
||||||
@ -59,32 +46,23 @@ type videoDeleteRequest struct {
|
|||||||
func (server *Server) videoView(w http.ResponseWriter, r *http.Request) {
|
func (server *Server) videoView(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
xid := vars["xid"]
|
xid := vars["xid"]
|
||||||
data := videoPageData{}
|
result, _ := server.store.GetVideo(r.Context(), xid)
|
||||||
video, err := server.store.GetVideo(r.Context(), xid)
|
server.renderLayout(w, r, result, "video/play.html.tmpl")
|
||||||
if err == nil {
|
|
||||||
data.Video = video
|
|
||||||
}
|
|
||||||
auth, err := server.withCookie(r)
|
|
||||||
if err == nil {
|
|
||||||
data.Authorize = *auth
|
|
||||||
}
|
|
||||||
server.renderLayout(w, r, data, "video/play.html.tmpl")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// videosView 视频列表页面
|
// videosView 视频列表页面
|
||||||
func (server *Server) videosView(w http.ResponseWriter, r *http.Request) {
|
func (server *Server) videosView(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
|
||||||
data := videosPageData{
|
|
||||||
Authorize: withUser(ctx),
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
page, err := strconv.Atoi(vars["page"])
|
page, err := strconv.Atoi(vars["page"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
page = 1
|
page = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
u := withUser(ctx)
|
||||||
|
var result []db.Video
|
||||||
videos, err := server.store.ListVideosByUser(ctx, db.ListVideosByUserParams{
|
videos, err := server.store.ListVideosByUser(ctx, db.ListVideosByUserParams{
|
||||||
UserID: data.Authorize.ID,
|
UserID: u.ID,
|
||||||
Limit: 16,
|
Limit: 16,
|
||||||
Offset: int32((page - 1) * 16),
|
Offset: int32((page - 1) * 16),
|
||||||
})
|
})
|
||||||
@ -94,28 +72,30 @@ func (server *Server) videosView(w http.ResponseWriter, r *http.Request) {
|
|||||||
temp := strings.TrimSpace(item.Description[0:65]) + "..."
|
temp := strings.TrimSpace(item.Description[0:65]) + "..."
|
||||||
item.Description = temp
|
item.Description = temp
|
||||||
}
|
}
|
||||||
data.Videos = append(data.Videos, item)
|
result = append(result, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server.renderLayout(w, r, data, "video/videos.html.tmpl")
|
server.renderLayout(w, r, result, "video/videos.html.tmpl")
|
||||||
}
|
}
|
||||||
|
|
||||||
// editVideoView 视频编辑页面
|
// editVideoView 视频编辑页面
|
||||||
func (server *Server) editVideoView(w http.ResponseWriter, r *http.Request) {
|
func (server *Server) editVideoView(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
xid := vars["xid"]
|
xid := vars["xid"]
|
||||||
vm := videoEditPageData{
|
ctx := r.Context()
|
||||||
Authorize: withUser(r.Context()),
|
u := withUser(ctx)
|
||||||
}
|
vm := videoEditPageData{}
|
||||||
if len(xid) > 0 {
|
if len(xid) > 0 {
|
||||||
if v, err := server.store.GetVideo(r.Context(), xid); err == nil {
|
if v, err := server.store.GetVideo(ctx, xid); err == nil {
|
||||||
vm.ID = v.ID
|
if u.ID == v.UserID {
|
||||||
vm.Title = v.Title
|
vm.ID = v.ID
|
||||||
vm.Images = v.Images
|
vm.Title = v.Title
|
||||||
vm.Description = v.Description
|
vm.Images = v.Images
|
||||||
vm.OriginLink = v.OriginLink
|
vm.Description = v.Description
|
||||||
vm.Status = int(v.Status)
|
vm.OriginLink = v.OriginLink
|
||||||
|
vm.Status = int(v.Status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server.renderEditVideo(w, r, vm)
|
server.renderEditVideo(w, r, vm)
|
||||||
@ -291,7 +271,6 @@ func viladatorEditVedio(r *http.Request) (videoEditPageData, bool) {
|
|||||||
ok := true
|
ok := true
|
||||||
status, _ := strconv.Atoi(r.PostFormValue("status"))
|
status, _ := strconv.Atoi(r.PostFormValue("status"))
|
||||||
resp := videoEditPageData{
|
resp := videoEditPageData{
|
||||||
Authorize: withUser(r.Context()),
|
|
||||||
ID: r.PostFormValue("id"),
|
ID: r.PostFormValue("id"),
|
||||||
Title: r.PostFormValue("title"),
|
Title: r.PostFormValue("title"),
|
||||||
Images: r.PostFormValue("images"),
|
Images: r.PostFormValue("images"),
|
||||||
|
|||||||
@ -21,9 +21,9 @@
|
|||||||
HLS流媒体
|
HLS流媒体
|
||||||
</a>
|
</a>
|
||||||
<ul class="flex oauth">
|
<ul class="flex oauth">
|
||||||
{{if .Authorize.ID}}
|
{{if currentUser}}
|
||||||
<li style="font-size: 12px;">
|
<li style="font-size: 12px;">
|
||||||
欢迎您: {{.Authorize.Name}}
|
欢迎您: {{ currentUser.Name }}
|
||||||
</li>
|
</li>
|
||||||
<li style="font-size: 12px;">
|
<li style="font-size: 12px;">
|
||||||
<a href="/me/videos">我的视频</a>
|
<a href="/me/videos">我的视频</a>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<div class="main">
|
<div class="main">
|
||||||
<h3 style="margin-top: 20px;margin-bottom: 10px;">视频列表</h3>
|
<h3 style="margin-top: 20px;margin-bottom: 10px;">视频列表</h3>
|
||||||
<div class="video-list">
|
<div class="video-list">
|
||||||
{{range .Videos}}
|
{{range .}}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<img src="{{.Images}}" class="card-img-top" alt="{{.Title}}">
|
<img src="{{.Images}}" class="card-img-top" alt="{{.Title}}">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{{template "header" .}}
|
{{template "header" .}}
|
||||||
<div class="container-fluid flex justify-content">
|
<div class="container-fluid flex justify-content">
|
||||||
<div class="main">
|
<div class="main">
|
||||||
{{if eq .Video.Status 200}}
|
{{if eq .Status 200}}
|
||||||
<h6 style="margin-top: 20px;margin-bottom: 10px;">正在播放
|
<h6 style="margin-top: 20px;margin-bottom: 10px;">正在播放
|
||||||
<svg class="icon" viewBox="0 0 1024 1024" width="15" height="21">
|
<svg class="icon" viewBox="0 0 1024 1024" width="15" height="21">
|
||||||
<path
|
<path
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<path
|
<path
|
||||||
d="M864.696118 992.000352c0-7.399919-2.599971-14.899836-7.799914-20.89977l-360.796029-416.99541c-20.999769-23.999736-20.999769-60.299336 0-84.299073l0.099999-0.099999L856.896204 52.910688c11.599872-13.399853 10.099889-33.59963-3.299964-45.099504-13.399853-11.599872-33.59963-10.099889-45.099503 3.299964L447.900705 427.806562c-20.399775 23.299744-31.599652 53.199414-31.599652 84.199073s11.199877 60.89933 31.599652 84.199073l360.596032 416.695414c11.599872 13.399853 31.79965 14.799837 45.099503 3.299964 7.399919-6.299931 11.099878-15.199833 11.099878-24.199734z"
|
d="M864.696118 992.000352c0-7.399919-2.599971-14.899836-7.799914-20.89977l-360.796029-416.99541c-20.999769-23.999736-20.999769-60.299336 0-84.299073l0.099999-0.099999L856.896204 52.910688c11.599872-13.399853 10.099889-33.59963-3.299964-45.099504-13.399853-11.599872-33.59963-10.099889-45.099503 3.299964L447.900705 427.806562c-20.399775 23.299744-31.599652 53.199414-31.599652 84.199073s11.199877 60.89933 31.599652 84.199073l360.596032 416.695414c11.599872 13.399853 31.79965 14.799837 45.099503 3.299964 7.399919-6.299931 11.099878-15.199833 11.099878-24.199734z"
|
||||||
p-id="6426"></path>
|
p-id="6426"></path>
|
||||||
</svg>{{.Video.Title}}<svg class="icon" viewBox="0 0 1024 1024" width="15" height="21">
|
</svg>{{.Title}}<svg class="icon" viewBox="0 0 1024 1024" width="15" height="21">
|
||||||
<path
|
<path
|
||||||
d="M416.301053 992.000352c0-7.399919 2.599971-14.899836 7.799914-20.89977l360.79603-416.895412c20.999769-23.999736 20.999769-60.299336 0-84.299072l-0.099999-0.099999L424.100967 52.910688c-11.599872-13.399853-10.099889-33.59963 3.299964-45.099504 13.399853-11.599872 33.59963-10.099889 45.099504 3.299964l360.596031 416.695414c20.399775 23.299744 31.599652 53.199414 31.599652 84.199073s-11.199877 60.89933-31.599652 84.199073l-360.596031 416.695414c-11.599872 13.399853-31.79965 14.799837-45.099504 3.299964-7.399919-6.299931-11.099878-15.199833-11.099878-24.199734z"
|
d="M416.301053 992.000352c0-7.399919 2.599971-14.899836 7.799914-20.89977l360.79603-416.895412c20.999769-23.999736 20.999769-60.299336 0-84.299072l-0.099999-0.099999L424.100967 52.910688c-11.599872-13.399853-10.099889-33.59963 3.299964-45.099504 13.399853-11.599872 33.59963-10.099889 45.099504 3.299964l360.596031 416.695414c20.399775 23.299744 31.599652 53.199414 31.599652 84.199073s-11.199877 60.89933-31.599652 84.199073l-360.596031 416.695414c-11.599872 13.399853-31.79965 14.799837-45.099504 3.299964-7.399919-6.299931-11.099878-15.199833-11.099878-24.199734z"
|
||||||
p-id="6274"></path>
|
p-id="6274"></path>
|
||||||
@ -46,14 +46,14 @@
|
|||||||
if (Hls.isSupported()) {
|
if (Hls.isSupported()) {
|
||||||
console.log("supported");
|
console.log("supported");
|
||||||
var hls = new Hls();
|
var hls = new Hls();
|
||||||
hls.loadSource('{{.Video.PlayLink}}');
|
hls.loadSource('{{.PlayLink}}');
|
||||||
hls.attachMedia(video);
|
hls.attachMedia(video);
|
||||||
hls.on(Hls.Events.MANIFEST_PARSED, function () {
|
hls.on(Hls.Events.MANIFEST_PARSED, function () {
|
||||||
//video.play();
|
//video.play();
|
||||||
});
|
});
|
||||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
console.log("no supported");
|
console.log("no supported");
|
||||||
video.src = '{{.Video.PlayLink}}';
|
video.src = '{{.PlayLink}}';
|
||||||
video.addEventListener('loadedmetadata', function () {
|
video.addEventListener('loadedmetadata', function () {
|
||||||
//video.play();
|
//video.play();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<a href="/me/videos/update" class="btn btn-primary">添加</a>
|
<a href="/me/videos/update" class="btn btn-primary">添加</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="video-list">
|
<div class="video-list">
|
||||||
{{range .Videos}}
|
{{range .}}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<img src="{{.Images}}" class="card-img-top" alt="{{.Title}}">
|
<img src="{{.Images}}" class="card-img-top" alt="{{.Title}}">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user