minor fixes

This commit is contained in:
Yared Yemane 2026-03-06 06:03:05 -08:00
parent 0226275d47
commit f9da45da62
11 changed files with 155 additions and 1 deletions

View File

@ -376,6 +376,13 @@ SET
updated_at = CURRENT_TIMESTAMP
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
UPDATE users
SET

View File

@ -5,7 +5,7 @@ services:
container_name: yimaru-backend-postgres-1
image: postgres:16-alpine
ports:
- "5432:5422"
- "5592:5422"
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_USER=root

View File

@ -764,6 +764,27 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
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
SELECT
CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique

View File

@ -181,6 +181,12 @@ type UpdateUserStatusReq struct {
UserID int64
}
type UserSummary struct {
TotalUsers int64 `json:"total_users"`
ActiveUsers int64 `json:"active_users"`
JoinedThisMonth int64 `json:"joined_this_month"`
}
type UpdateUserReq struct {
UserID int64 `json:"-"`

View File

@ -58,6 +58,7 @@ type UserStore interface {
limit, offset int32,
) ([]domain.User, int64, error)
GetTotalUsers(ctx context.Context, role *string) (int64, error)
GetUserSummary(ctx context.Context) (domain.UserSummary, error)
SearchUserByNameOrPhone(
ctx context.Context,
search string,

View File

@ -515,6 +515,19 @@ func (s *Store) GetAllUsers(
}
// 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) {
count, err := s.queries.GetTotalUsers(ctx, *role)
if err != nil {

View File

@ -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 {
return s.userStore.UpdateUserStatus(ctx, req)
}

View File

@ -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 {
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
}

View File

@ -22,6 +22,33 @@ import (
"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
// @Summary Check if user profile is completed
// @Description Returns the profile completion status and percentage for the specified user

View File

@ -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
// @Summary Get oEmbed data for a Vimeo URL
// @Description Fetches oEmbed metadata for a Vimeo video URL

View File

@ -209,6 +209,7 @@ func (a *App) initAppRoutes() {
// User Routes
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/summary", a.authMiddleware, a.RequirePermission("users.summary"), h.GetUserSummary)
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.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/tus", a.authMiddleware, a.RequirePermission("vimeo.uploads.tus"), h.CreateTusUpload)
vimeoGroup.Get("/oembed", h.GetOEmbed)
vimeoGroup.Get("/sample", h.GetSampleVideo)
// Team Management
teamGroup := groupV1.Group("/team")