minor fixes
This commit is contained in:
parent
0226275d47
commit
f9da45da62
|
|
@ -376,6 +376,13 @@ SET
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = $2;
|
WHERE id = $2;
|
||||||
|
|
||||||
|
-- name: GetUserSummary :one
|
||||||
|
SELECT
|
||||||
|
COUNT(*) AS total_users,
|
||||||
|
COUNT(*) FILTER (WHERE status = 'ACTIVE') AS active_users,
|
||||||
|
COUNT(*) FILTER (WHERE created_at >= date_trunc('month', CURRENT_DATE)) AS joined_this_month
|
||||||
|
FROM users;
|
||||||
|
|
||||||
-- name: UpdateUserKnowledgeLevel :exec
|
-- name: UpdateUserKnowledgeLevel :exec
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET
|
SET
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ services:
|
||||||
container_name: yimaru-backend-postgres-1
|
container_name: yimaru-backend-postgres-1
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
ports:
|
ports:
|
||||||
- "5432:5422"
|
- "5592:5422"
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_PASSWORD=secret
|
- POSTGRES_PASSWORD=secret
|
||||||
- POSTGRES_USER=root
|
- POSTGRES_USER=root
|
||||||
|
|
|
||||||
|
|
@ -764,6 +764,27 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetUserSummary = `-- name: GetUserSummary :one
|
||||||
|
SELECT
|
||||||
|
COUNT(*) AS total_users,
|
||||||
|
COUNT(*) FILTER (WHERE status = 'ACTIVE') AS active_users,
|
||||||
|
COUNT(*) FILTER (WHERE created_at >= date_trunc('month', CURRENT_DATE)) AS joined_this_month
|
||||||
|
FROM users
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetUserSummaryRow struct {
|
||||||
|
TotalUsers int64 `json:"total_users"`
|
||||||
|
ActiveUsers int64 `json:"active_users"`
|
||||||
|
JoinedThisMonth int64 `json:"joined_this_month"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUserSummary(ctx context.Context) (GetUserSummaryRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetUserSummary)
|
||||||
|
var i GetUserSummaryRow
|
||||||
|
err := row.Scan(&i.TotalUsers, &i.ActiveUsers, &i.JoinedThisMonth)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const IsUserNameUnique = `-- name: IsUserNameUnique :one
|
const IsUserNameUnique = `-- name: IsUserNameUnique :one
|
||||||
SELECT
|
SELECT
|
||||||
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique
|
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,12 @@ type UpdateUserStatusReq struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserSummary struct {
|
||||||
|
TotalUsers int64 `json:"total_users"`
|
||||||
|
ActiveUsers int64 `json:"active_users"`
|
||||||
|
JoinedThisMonth int64 `json:"joined_this_month"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateUserReq struct {
|
type UpdateUserReq struct {
|
||||||
UserID int64 `json:"-"`
|
UserID int64 `json:"-"`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ type UserStore interface {
|
||||||
limit, offset int32,
|
limit, offset int32,
|
||||||
) ([]domain.User, int64, error)
|
) ([]domain.User, int64, error)
|
||||||
GetTotalUsers(ctx context.Context, role *string) (int64, error)
|
GetTotalUsers(ctx context.Context, role *string) (int64, error)
|
||||||
|
GetUserSummary(ctx context.Context) (domain.UserSummary, error)
|
||||||
SearchUserByNameOrPhone(
|
SearchUserByNameOrPhone(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
search string,
|
search string,
|
||||||
|
|
|
||||||
|
|
@ -515,6 +515,19 @@ func (s *Store) GetAllUsers(
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTotalUsers counts users with optional filters
|
// GetTotalUsers counts users with optional filters
|
||||||
|
func (s *Store) GetUserSummary(ctx context.Context) (domain.UserSummary, error) {
|
||||||
|
res, err := s.queries.GetUserSummary(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return domain.UserSummary{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.UserSummary{
|
||||||
|
TotalUsers: res.TotalUsers,
|
||||||
|
ActiveUsers: res.ActiveUsers,
|
||||||
|
JoinedThisMonth: res.JoinedThisMonth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Store) GetTotalUsers(ctx context.Context, role *string) (int64, error) {
|
func (s *Store) GetTotalUsers(ctx context.Context, role *string) (int64, error) {
|
||||||
count, err := s.queries.GetTotalUsers(ctx, *role)
|
count, err := s.queries.GetTotalUsers(ctx, *role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,10 @@ func (s *Service) GetAllUsers(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetUserSummary(ctx context.Context) (domain.UserSummary, error) {
|
||||||
|
return s.userStore.GetUserSummary(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) UpdateUserStatus(ctx context.Context, req domain.UpdateUserStatusReq) error {
|
func (s *Service) UpdateUserStatus(ctx context.Context, req domain.UpdateUserStatusReq) error {
|
||||||
return s.userStore.UpdateUserStatus(ctx, req)
|
return s.userStore.UpdateUserStatus(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -195,3 +195,19 @@ func (s *Service) GetOEmbed(ctx context.Context, vimeoURL string, width, height
|
||||||
func (s *Service) GeneratePlayerURL(videoID string, opts *vimeo.EmbedOptions) string {
|
func (s *Service) GeneratePlayerURL(videoID string, opts *vimeo.EmbedOptions) string {
|
||||||
return vimeo.GenerateEmbedURL(videoID, opts)
|
return vimeo.GenerateEmbedURL(videoID, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSampleVideo fetches a public Vimeo video by ID and returns its info along with an embeddable iframe.
|
||||||
|
func (s *Service) GetSampleVideo(ctx context.Context, videoID string, width, height int) (*VideoInfo, string, error) {
|
||||||
|
info, err := s.GetVideoInfo(ctx, videoID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to get sample video: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe := vimeo.GenerateIframeEmbed(videoID, width, height, &vimeo.EmbedOptions{
|
||||||
|
Title: true,
|
||||||
|
Byline: true,
|
||||||
|
Portrait: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return info, iframe, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,33 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetUserSummary godoc
|
||||||
|
// @Summary Get user summary statistics
|
||||||
|
// @Description Returns total users, active users, and users who joined this month
|
||||||
|
// @Tags user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Success 200 {object} domain.Response{data=domain.UserSummary}
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/users/summary [get]
|
||||||
|
func (h *Handler) GetUserSummary(c *fiber.Ctx) error {
|
||||||
|
summary, err := h.userSvc.GetUserSummary(c.Context())
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to get user summary",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||||
|
Message: "User summary retrieved successfully",
|
||||||
|
Data: summary,
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// CheckProfileCompleted godoc
|
// CheckProfileCompleted godoc
|
||||||
// @Summary Check if user profile is completed
|
// @Summary Check if user profile is completed
|
||||||
// @Description Returns the profile completion status and percentage for the specified user
|
// @Description Returns the profile completion status and percentage for the specified user
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,63 @@ func (h *Handler) GetTranscodeStatus(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSampleVideo godoc
|
||||||
|
// @Summary Get a sample Vimeo video with iframe embed
|
||||||
|
// @Description Fetches a sample video from Vimeo and returns video details along with an embeddable iframe for client-side integration
|
||||||
|
// @Tags Vimeo
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param video_id query string false "Vimeo Video ID to use as sample" default(76979871)
|
||||||
|
// @Param width query int false "Player width" default(640)
|
||||||
|
// @Param height query int false "Player height" default(360)
|
||||||
|
// @Success 200 {object} domain.Response
|
||||||
|
// @Failure 500 {object} domain.ErrorResponse
|
||||||
|
// @Router /api/v1/vimeo/sample [get]
|
||||||
|
func (h *Handler) GetSampleVideo(c *fiber.Ctx) error {
|
||||||
|
if h.vimeoSvc == nil {
|
||||||
|
return c.Status(fiber.StatusServiceUnavailable).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Vimeo service is not configured",
|
||||||
|
Error: "Vimeo service is not enabled or missing access token",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
videoID := c.Query("video_id", "76979871")
|
||||||
|
width, _ := strconv.Atoi(c.Query("width", "640"))
|
||||||
|
height, _ := strconv.Atoi(c.Query("height", "360"))
|
||||||
|
|
||||||
|
info, iframe, err := h.vimeoSvc.GetSampleVideo(c.Context(), videoID, width, height)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||||
|
Message: "Failed to get sample video",
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(domain.Response{
|
||||||
|
Message: "Sample video retrieved successfully",
|
||||||
|
Data: fiber.Map{
|
||||||
|
"video": VimeoVideoResponse{
|
||||||
|
VimeoID: info.VimeoID,
|
||||||
|
URI: info.URI,
|
||||||
|
Name: info.Name,
|
||||||
|
Description: info.Description,
|
||||||
|
Duration: info.Duration,
|
||||||
|
Width: info.Width,
|
||||||
|
Height: info.Height,
|
||||||
|
Link: info.Link,
|
||||||
|
EmbedURL: info.EmbedURL,
|
||||||
|
EmbedHTML: info.EmbedHTML,
|
||||||
|
ThumbnailURL: info.ThumbnailURL,
|
||||||
|
Status: info.Status,
|
||||||
|
TranscodeStatus: info.TranscodeStatus,
|
||||||
|
},
|
||||||
|
"iframe": iframe,
|
||||||
|
},
|
||||||
|
Success: true,
|
||||||
|
StatusCode: fiber.StatusOK,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GetOEmbed godoc
|
// GetOEmbed godoc
|
||||||
// @Summary Get oEmbed data for a Vimeo URL
|
// @Summary Get oEmbed data for a Vimeo URL
|
||||||
// @Description Fetches oEmbed metadata for a Vimeo video URL
|
// @Description Fetches oEmbed metadata for a Vimeo video URL
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,7 @@ func (a *App) initAppRoutes() {
|
||||||
// User Routes
|
// User Routes
|
||||||
groupV1.Get("/user/:user_id/is-profile-completed", a.authMiddleware, a.RequirePermission("users.profile_completed"), h.CheckProfileCompleted)
|
groupV1.Get("/user/:user_id/is-profile-completed", a.authMiddleware, a.RequirePermission("users.profile_completed"), h.CheckProfileCompleted)
|
||||||
groupV1.Get("/users", a.authMiddleware, a.RequirePermission("users.list"), h.GetAllUsers)
|
groupV1.Get("/users", a.authMiddleware, a.RequirePermission("users.list"), h.GetAllUsers)
|
||||||
|
groupV1.Get("/users/summary", a.authMiddleware, a.RequirePermission("users.summary"), h.GetUserSummary)
|
||||||
groupV1.Put("/user", a.authMiddleware, a.RequirePermission("users.update_self"), h.UpdateUser)
|
groupV1.Put("/user", a.authMiddleware, a.RequirePermission("users.update_self"), h.UpdateUser)
|
||||||
groupV1.Patch("/user/status", a.authMiddleware, a.RequirePermission("users.update_status"), h.UpdateUserStatus)
|
groupV1.Patch("/user/status", a.authMiddleware, a.RequirePermission("users.update_status"), h.UpdateUserStatus)
|
||||||
groupV1.Put("/user/knowledge-level", h.UpdateUserKnowledgeLevel)
|
groupV1.Put("/user/knowledge-level", h.UpdateUserKnowledgeLevel)
|
||||||
|
|
@ -292,6 +293,7 @@ func (a *App) initAppRoutes() {
|
||||||
vimeoGroup.Post("/uploads/pull", a.authMiddleware, a.RequirePermission("vimeo.uploads.pull"), h.CreatePullUpload)
|
vimeoGroup.Post("/uploads/pull", a.authMiddleware, a.RequirePermission("vimeo.uploads.pull"), h.CreatePullUpload)
|
||||||
vimeoGroup.Post("/uploads/tus", a.authMiddleware, a.RequirePermission("vimeo.uploads.tus"), h.CreateTusUpload)
|
vimeoGroup.Post("/uploads/tus", a.authMiddleware, a.RequirePermission("vimeo.uploads.tus"), h.CreateTusUpload)
|
||||||
vimeoGroup.Get("/oembed", h.GetOEmbed)
|
vimeoGroup.Get("/oembed", h.GetOEmbed)
|
||||||
|
vimeoGroup.Get("/sample", h.GetSampleVideo)
|
||||||
|
|
||||||
// Team Management
|
// Team Management
|
||||||
teamGroup := groupV1.Group("/team")
|
teamGroup := groupV1.Group("/team")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user