Add paginated Vimeo video list API (GET /me/videos).
Exposes the Vimeo account library for admin workflows and syncs swagger docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9afc9a4392
commit
7f8ef3373c
555
docs/docs.go
555
docs/docs.go
|
|
@ -702,6 +702,64 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/analytics/dashboard": {
|
||||
"get": {
|
||||
"description": "Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"analytics"
|
||||
],
|
||||
"summary": "Analytics dashboard",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Calendar year (e.g. 2025)",
|
||||
"name": "year",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Calendar month 1-12 (requires year)",
|
||||
"name": "month",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom range start (YYYY-MM-DD or RFC3339)",
|
||||
"name": "from",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom range end (YYYY-MM-DD or RFC3339, inclusive)",
|
||||
"name": "to",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.AnalyticsDashboard"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/assessment/questions": {
|
||||
"get": {
|
||||
"description": "Returns all active assessment questions from the initial assessment set",
|
||||
|
|
@ -8701,6 +8759,105 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/vimeo/videos": {
|
||||
"get": {
|
||||
"description": "Returns a paginated list of videos for the Vimeo API token (GET https://api.vimeo.com/me/videos)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Vimeo"
|
||||
],
|
||||
"summary": "List videos stored in the Vimeo account",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "Page number (starts at 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 25,
|
||||
"description": "Page size (Vimeo max 100)",
|
||||
"name": "per_page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Sort field (e.g. date, alphabetical, plays, likes, comments, duration, relevance)",
|
||||
"name": "sort",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "asc or desc",
|
||||
"name": "direction",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Vimeo filter (e.g. embeddable, playable)",
|
||||
"name": "filter",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Vimeo filter_type when using filter",
|
||||
"name": "filter_type",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.VimeoVideoResponse"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/handlers.VimeoVideosListMetadata"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/vimeo/videos/{video_id}": {
|
||||
"get": {
|
||||
"description": "Retrieves video details from Vimeo by video ID",
|
||||
|
|
@ -9455,6 +9612,375 @@ const docTemplate = `{
|
|||
"Age55Plus"
|
||||
]
|
||||
},
|
||||
"domain.AnalyticsContentSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question_sets_by_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"questions_by_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"total_question_sets": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_questions": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsCoursesSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total_categories": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_courses": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_sub_courses": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_videos": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsDashboard": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"$ref": "#/definitions/domain.AnalyticsContentSection"
|
||||
},
|
||||
"courses": {
|
||||
"$ref": "#/definitions/domain.AnalyticsCoursesSection"
|
||||
},
|
||||
"date_filter": {
|
||||
"$ref": "#/definitions/domain.AnalyticsDateFilter"
|
||||
},
|
||||
"generated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"issues": {
|
||||
"$ref": "#/definitions/domain.AnalyticsIssuesSection"
|
||||
},
|
||||
"notifications": {
|
||||
"$ref": "#/definitions/domain.AnalyticsNotificationsSection"
|
||||
},
|
||||
"payments": {
|
||||
"$ref": "#/definitions/domain.AnalyticsPaymentsSection"
|
||||
},
|
||||
"subscriptions": {
|
||||
"$ref": "#/definitions/domain.AnalyticsSubscriptionsSection"
|
||||
},
|
||||
"team": {
|
||||
"$ref": "#/definitions/domain.AnalyticsTeamSection"
|
||||
},
|
||||
"users": {
|
||||
"$ref": "#/definitions/domain.AnalyticsUsersSection"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsDateFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"month": {
|
||||
"type": "integer"
|
||||
},
|
||||
"range_end": {
|
||||
"type": "string"
|
||||
},
|
||||
"range_start": {
|
||||
"type": "string"
|
||||
},
|
||||
"ref_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"series_end": {
|
||||
"type": "string"
|
||||
},
|
||||
"series_start": {
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"type": "string"
|
||||
},
|
||||
"year": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsIssuesSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"resolution_rate": {
|
||||
"type": "number"
|
||||
},
|
||||
"resolved_issues": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_issues": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsLabelAmount": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsLabelCount": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsNotificationsSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"by_channel": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"read_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_sent": {
|
||||
"type": "integer"
|
||||
},
|
||||
"unread_count": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsPaymentsSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avg_transaction_value": {
|
||||
"type": "number"
|
||||
},
|
||||
"by_method": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelAmount"
|
||||
}
|
||||
},
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelAmount"
|
||||
}
|
||||
},
|
||||
"revenue_last_30_days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsRevenueTimePoint"
|
||||
}
|
||||
},
|
||||
"successful_payments": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_payments": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_revenue": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsRevenueByPlan": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"plan_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"revenue": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsRevenueTimePoint": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"revenue": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsSubscriptionsSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active_subscriptions": {
|
||||
"type": "integer"
|
||||
},
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"new_month": {
|
||||
"type": "integer"
|
||||
},
|
||||
"new_subscriptions_last_30_days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsTimePoint"
|
||||
}
|
||||
},
|
||||
"new_today": {
|
||||
"type": "integer"
|
||||
},
|
||||
"new_week": {
|
||||
"type": "integer"
|
||||
},
|
||||
"revenue_by_plan": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsRevenueByPlan"
|
||||
}
|
||||
},
|
||||
"total_subscriptions": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsTeamSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"by_role": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"total_members": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsTimePoint": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsUsersSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"by_age_group": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_knowledge_level": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_region": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_role": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"new_month": {
|
||||
"type": "integer"
|
||||
},
|
||||
"new_today": {
|
||||
"type": "integer"
|
||||
},
|
||||
"new_week": {
|
||||
"type": "integer"
|
||||
},
|
||||
"registrations_last_30_days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsTimePoint"
|
||||
}
|
||||
},
|
||||
"total_users": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateCourseInput": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -11198,6 +11724,35 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.VimeoVideosListMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current_page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"first": {
|
||||
"type": "string"
|
||||
},
|
||||
"last": {
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"next": {
|
||||
"type": "string"
|
||||
},
|
||||
"previous": {
|
||||
"type": "string"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.addQuestionToSetReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -694,6 +694,64 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/analytics/dashboard": {
|
||||
"get": {
|
||||
"description": "Platform analytics with optional date filters: all-time (default), year, year+month, or custom from/to range.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"analytics"
|
||||
],
|
||||
"summary": "Analytics dashboard",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Calendar year (e.g. 2025)",
|
||||
"name": "year",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Calendar month 1-12 (requires year)",
|
||||
"name": "month",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom range start (YYYY-MM-DD or RFC3339)",
|
||||
"name": "from",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Custom range end (YYYY-MM-DD or RFC3339, inclusive)",
|
||||
"name": "to",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.AnalyticsDashboard"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/assessment/questions": {
|
||||
"get": {
|
||||
"description": "Returns all active assessment questions from the initial assessment set",
|
||||
|
|
@ -8693,6 +8751,105 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/vimeo/videos": {
|
||||
"get": {
|
||||
"description": "Returns a paginated list of videos for the Vimeo API token (GET https://api.vimeo.com/me/videos)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Vimeo"
|
||||
],
|
||||
"summary": "List videos stored in the Vimeo account",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "Page number (starts at 1)",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 25,
|
||||
"description": "Page size (Vimeo max 100)",
|
||||
"name": "per_page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Sort field (e.g. date, alphabetical, plays, likes, comments, duration, relevance)",
|
||||
"name": "sort",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "asc or desc",
|
||||
"name": "direction",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Vimeo filter (e.g. embeddable, playable)",
|
||||
"name": "filter",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Vimeo filter_type when using filter",
|
||||
"name": "filter_type",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/domain.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/handlers.VimeoVideoResponse"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"$ref": "#/definitions/handlers.VimeoVideosListMetadata"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/domain.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/vimeo/videos/{video_id}": {
|
||||
"get": {
|
||||
"description": "Retrieves video details from Vimeo by video ID",
|
||||
|
|
@ -9447,6 +9604,375 @@
|
|||
"Age55Plus"
|
||||
]
|
||||
},
|
||||
"domain.AnalyticsContentSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question_sets_by_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"questions_by_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"total_question_sets": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_questions": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsCoursesSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total_categories": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_courses": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_sub_courses": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_videos": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsDashboard": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"$ref": "#/definitions/domain.AnalyticsContentSection"
|
||||
},
|
||||
"courses": {
|
||||
"$ref": "#/definitions/domain.AnalyticsCoursesSection"
|
||||
},
|
||||
"date_filter": {
|
||||
"$ref": "#/definitions/domain.AnalyticsDateFilter"
|
||||
},
|
||||
"generated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"issues": {
|
||||
"$ref": "#/definitions/domain.AnalyticsIssuesSection"
|
||||
},
|
||||
"notifications": {
|
||||
"$ref": "#/definitions/domain.AnalyticsNotificationsSection"
|
||||
},
|
||||
"payments": {
|
||||
"$ref": "#/definitions/domain.AnalyticsPaymentsSection"
|
||||
},
|
||||
"subscriptions": {
|
||||
"$ref": "#/definitions/domain.AnalyticsSubscriptionsSection"
|
||||
},
|
||||
"team": {
|
||||
"$ref": "#/definitions/domain.AnalyticsTeamSection"
|
||||
},
|
||||
"users": {
|
||||
"$ref": "#/definitions/domain.AnalyticsUsersSection"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsDateFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"from": {
|
||||
"type": "string"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"month": {
|
||||
"type": "integer"
|
||||
},
|
||||
"range_end": {
|
||||
"type": "string"
|
||||
},
|
||||
"range_start": {
|
||||
"type": "string"
|
||||
},
|
||||
"ref_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"series_end": {
|
||||
"type": "string"
|
||||
},
|
||||
"series_start": {
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"type": "string"
|
||||
},
|
||||
"year": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsIssuesSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"resolution_rate": {
|
||||
"type": "number"
|
||||
},
|
||||
"resolved_issues": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_issues": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsLabelAmount": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "number"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsLabelCount": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsNotificationsSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"by_channel": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_type": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"read_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_sent": {
|
||||
"type": "integer"
|
||||
},
|
||||
"unread_count": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsPaymentsSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avg_transaction_value": {
|
||||
"type": "number"
|
||||
},
|
||||
"by_method": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelAmount"
|
||||
}
|
||||
},
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelAmount"
|
||||
}
|
||||
},
|
||||
"revenue_last_30_days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsRevenueTimePoint"
|
||||
}
|
||||
},
|
||||
"successful_payments": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_payments": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_revenue": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsRevenueByPlan": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currency": {
|
||||
"type": "string"
|
||||
},
|
||||
"plan_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"revenue": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsRevenueTimePoint": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"revenue": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsSubscriptionsSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"active_subscriptions": {
|
||||
"type": "integer"
|
||||
},
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"new_month": {
|
||||
"type": "integer"
|
||||
},
|
||||
"new_subscriptions_last_30_days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsTimePoint"
|
||||
}
|
||||
},
|
||||
"new_today": {
|
||||
"type": "integer"
|
||||
},
|
||||
"new_week": {
|
||||
"type": "integer"
|
||||
},
|
||||
"revenue_by_plan": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsRevenueByPlan"
|
||||
}
|
||||
},
|
||||
"total_subscriptions": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsTeamSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"by_role": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"total_members": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsTimePoint": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.AnalyticsUsersSection": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"by_age_group": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_knowledge_level": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_region": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_role": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"by_status": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsLabelCount"
|
||||
}
|
||||
},
|
||||
"new_month": {
|
||||
"type": "integer"
|
||||
},
|
||||
"new_today": {
|
||||
"type": "integer"
|
||||
},
|
||||
"new_week": {
|
||||
"type": "integer"
|
||||
},
|
||||
"registrations_last_30_days": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/domain.AnalyticsTimePoint"
|
||||
}
|
||||
},
|
||||
"total_users": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain.CreateCourseInput": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
@ -11190,6 +11716,35 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"handlers.VimeoVideosListMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current_page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"first": {
|
||||
"type": "string"
|
||||
},
|
||||
"last": {
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"next": {
|
||||
"type": "string"
|
||||
},
|
||||
"previous": {
|
||||
"type": "string"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total_pages": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handlers.addQuestionToSetReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
|
|
|||
|
|
@ -17,6 +17,247 @@ definitions:
|
|||
- Age35To44
|
||||
- Age45To54
|
||||
- Age55Plus
|
||||
domain.AnalyticsContentSection:
|
||||
properties:
|
||||
question_sets_by_type:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
questions_by_type:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
total_question_sets:
|
||||
type: integer
|
||||
total_questions:
|
||||
type: integer
|
||||
type: object
|
||||
domain.AnalyticsCoursesSection:
|
||||
properties:
|
||||
total_categories:
|
||||
type: integer
|
||||
total_courses:
|
||||
type: integer
|
||||
total_sub_courses:
|
||||
type: integer
|
||||
total_videos:
|
||||
type: integer
|
||||
type: object
|
||||
domain.AnalyticsDashboard:
|
||||
properties:
|
||||
content:
|
||||
$ref: '#/definitions/domain.AnalyticsContentSection'
|
||||
courses:
|
||||
$ref: '#/definitions/domain.AnalyticsCoursesSection'
|
||||
date_filter:
|
||||
$ref: '#/definitions/domain.AnalyticsDateFilter'
|
||||
generated_at:
|
||||
type: string
|
||||
issues:
|
||||
$ref: '#/definitions/domain.AnalyticsIssuesSection'
|
||||
notifications:
|
||||
$ref: '#/definitions/domain.AnalyticsNotificationsSection'
|
||||
payments:
|
||||
$ref: '#/definitions/domain.AnalyticsPaymentsSection'
|
||||
subscriptions:
|
||||
$ref: '#/definitions/domain.AnalyticsSubscriptionsSection'
|
||||
team:
|
||||
$ref: '#/definitions/domain.AnalyticsTeamSection'
|
||||
users:
|
||||
$ref: '#/definitions/domain.AnalyticsUsersSection'
|
||||
type: object
|
||||
domain.AnalyticsDateFilter:
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
mode:
|
||||
type: string
|
||||
month:
|
||||
type: integer
|
||||
range_end:
|
||||
type: string
|
||||
range_start:
|
||||
type: string
|
||||
ref_date:
|
||||
type: string
|
||||
series_end:
|
||||
type: string
|
||||
series_start:
|
||||
type: string
|
||||
to:
|
||||
type: string
|
||||
year:
|
||||
type: integer
|
||||
type: object
|
||||
domain.AnalyticsIssuesSection:
|
||||
properties:
|
||||
by_status:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
by_type:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
resolution_rate:
|
||||
type: number
|
||||
resolved_issues:
|
||||
type: integer
|
||||
total_issues:
|
||||
type: integer
|
||||
type: object
|
||||
domain.AnalyticsLabelAmount:
|
||||
properties:
|
||||
amount:
|
||||
type: number
|
||||
count:
|
||||
type: integer
|
||||
label:
|
||||
type: string
|
||||
type: object
|
||||
domain.AnalyticsLabelCount:
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
label:
|
||||
type: string
|
||||
type: object
|
||||
domain.AnalyticsNotificationsSection:
|
||||
properties:
|
||||
by_channel:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
by_type:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
read_count:
|
||||
type: integer
|
||||
total_sent:
|
||||
type: integer
|
||||
unread_count:
|
||||
type: integer
|
||||
type: object
|
||||
domain.AnalyticsPaymentsSection:
|
||||
properties:
|
||||
avg_transaction_value:
|
||||
type: number
|
||||
by_method:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelAmount'
|
||||
type: array
|
||||
by_status:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelAmount'
|
||||
type: array
|
||||
revenue_last_30_days:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsRevenueTimePoint'
|
||||
type: array
|
||||
successful_payments:
|
||||
type: integer
|
||||
total_payments:
|
||||
type: integer
|
||||
total_revenue:
|
||||
type: number
|
||||
type: object
|
||||
domain.AnalyticsRevenueByPlan:
|
||||
properties:
|
||||
currency:
|
||||
type: string
|
||||
plan_name:
|
||||
type: string
|
||||
revenue:
|
||||
type: number
|
||||
type: object
|
||||
domain.AnalyticsRevenueTimePoint:
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
revenue:
|
||||
type: number
|
||||
type: object
|
||||
domain.AnalyticsSubscriptionsSection:
|
||||
properties:
|
||||
active_subscriptions:
|
||||
type: integer
|
||||
by_status:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
new_month:
|
||||
type: integer
|
||||
new_subscriptions_last_30_days:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsTimePoint'
|
||||
type: array
|
||||
new_today:
|
||||
type: integer
|
||||
new_week:
|
||||
type: integer
|
||||
revenue_by_plan:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsRevenueByPlan'
|
||||
type: array
|
||||
total_subscriptions:
|
||||
type: integer
|
||||
type: object
|
||||
domain.AnalyticsTeamSection:
|
||||
properties:
|
||||
by_role:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
by_status:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
total_members:
|
||||
type: integer
|
||||
type: object
|
||||
domain.AnalyticsTimePoint:
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
date:
|
||||
type: string
|
||||
type: object
|
||||
domain.AnalyticsUsersSection:
|
||||
properties:
|
||||
by_age_group:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
by_knowledge_level:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
by_region:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
by_role:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
by_status:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsLabelCount'
|
||||
type: array
|
||||
new_month:
|
||||
type: integer
|
||||
new_today:
|
||||
type: integer
|
||||
new_week:
|
||||
type: integer
|
||||
registrations_last_30_days:
|
||||
items:
|
||||
$ref: '#/definitions/domain.AnalyticsTimePoint'
|
||||
type: array
|
||||
total_users:
|
||||
type: integer
|
||||
type: object
|
||||
domain.CreateCourseInput:
|
||||
properties:
|
||||
description:
|
||||
|
|
@ -1190,6 +1431,25 @@ definitions:
|
|||
width:
|
||||
type: integer
|
||||
type: object
|
||||
handlers.VimeoVideosListMetadata:
|
||||
properties:
|
||||
current_page:
|
||||
type: integer
|
||||
first:
|
||||
type: string
|
||||
last:
|
||||
type: string
|
||||
limit:
|
||||
type: integer
|
||||
next:
|
||||
type: string
|
||||
previous:
|
||||
type: string
|
||||
total:
|
||||
type: integer
|
||||
total_pages:
|
||||
type: integer
|
||||
type: object
|
||||
handlers.addQuestionToSetReq:
|
||||
properties:
|
||||
display_order:
|
||||
|
|
@ -2602,6 +2862,45 @@ paths:
|
|||
summary: List account deletion requests
|
||||
tags:
|
||||
- user
|
||||
/api/v1/analytics/dashboard:
|
||||
get:
|
||||
description: 'Platform analytics with optional date filters: all-time (default),
|
||||
year, year+month, or custom from/to range.'
|
||||
parameters:
|
||||
- description: Calendar year (e.g. 2025)
|
||||
in: query
|
||||
name: year
|
||||
type: integer
|
||||
- description: Calendar month 1-12 (requires year)
|
||||
in: query
|
||||
name: month
|
||||
type: integer
|
||||
- description: Custom range start (YYYY-MM-DD or RFC3339)
|
||||
in: query
|
||||
name: from
|
||||
type: string
|
||||
- description: Custom range end (YYYY-MM-DD or RFC3339, inclusive)
|
||||
in: query
|
||||
name: to
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/domain.AnalyticsDashboard'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: Analytics dashboard
|
||||
tags:
|
||||
- analytics
|
||||
/api/v1/assessment/questions:
|
||||
get:
|
||||
description: Returns all active assessment questions from the initial assessment
|
||||
|
|
@ -7824,6 +8123,71 @@ paths:
|
|||
summary: Create a TUS resumable upload to Vimeo
|
||||
tags:
|
||||
- Vimeo
|
||||
/api/v1/vimeo/videos:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a paginated list of videos for the Vimeo API token (GET
|
||||
https://api.vimeo.com/me/videos)
|
||||
parameters:
|
||||
- default: 1
|
||||
description: Page number (starts at 1)
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- default: 25
|
||||
description: Page size (Vimeo max 100)
|
||||
in: query
|
||||
name: per_page
|
||||
type: integer
|
||||
- description: Search query
|
||||
in: query
|
||||
name: query
|
||||
type: string
|
||||
- description: Sort field (e.g. date, alphabetical, plays, likes, comments,
|
||||
duration, relevance)
|
||||
in: query
|
||||
name: sort
|
||||
type: string
|
||||
- description: asc or desc
|
||||
in: query
|
||||
name: direction
|
||||
type: string
|
||||
- description: Vimeo filter (e.g. embeddable, playable)
|
||||
in: query
|
||||
name: filter
|
||||
type: string
|
||||
- description: Vimeo filter_type when using filter
|
||||
in: query
|
||||
name: filter_type
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/domain.Response'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/handlers.VimeoVideoResponse'
|
||||
type: array
|
||||
metadata:
|
||||
$ref: '#/definitions/handlers.VimeoVideosListMetadata'
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
"503":
|
||||
description: Service Unavailable
|
||||
schema:
|
||||
$ref: '#/definitions/domain.ErrorResponse'
|
||||
summary: List videos stored in the Vimeo account
|
||||
tags:
|
||||
- Vimeo
|
||||
/api/v1/vimeo/videos/{video_id}:
|
||||
delete:
|
||||
consumes:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
|
@ -141,6 +142,35 @@ type UpdateVideoRequest struct {
|
|||
Privacy *PrivacyParams `json:"privacy,omitempty"`
|
||||
}
|
||||
|
||||
// ListVideosParams configures GET /me/videos (authenticated user’s library).
|
||||
// See https://developer.vimeo.com/api/reference/videos#get_videos
|
||||
type ListVideosParams struct {
|
||||
Page int // 1-based; omitted when 0
|
||||
PerPage int // max 100; omitted when 0
|
||||
Query string // optional search filter
|
||||
Sort string // e.g. date, alphabetical, plays, likes, comments, duration, relevance
|
||||
Direction string // asc or desc
|
||||
Filter string // optional: embeddable, playable, playable_in_subscription, etc.
|
||||
FilterType string // optional: 8 for staff picks (when using filter)
|
||||
}
|
||||
|
||||
// ListVideosResponse is the JSON envelope Vimeo returns for list endpoints.
|
||||
type ListVideosResponse struct {
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"per_page"`
|
||||
Paging PagingLinks `json:"paging"`
|
||||
Data []Video `json:"data"`
|
||||
}
|
||||
|
||||
// PagingLinks contains cursor URLs for the next/previous page from Vimeo.
|
||||
type PagingLinks struct {
|
||||
Next string `json:"next"`
|
||||
Previous string `json:"previous"`
|
||||
First string `json:"first"`
|
||||
Last string `json:"last"`
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
|
|
@ -185,6 +215,55 @@ func (c *Client) GetVideo(ctx context.Context, videoID string) (*Video, error) {
|
|||
return &video, nil
|
||||
}
|
||||
|
||||
// ListMyVideos calls GET /me/videos for the token’s Vimeo account.
|
||||
func (c *Client) ListMyVideos(ctx context.Context, params ListVideosParams) (*ListVideosResponse, error) {
|
||||
q := url.Values{}
|
||||
if params.Page > 0 {
|
||||
q.Set("page", strconv.Itoa(params.Page))
|
||||
}
|
||||
if params.PerPage > 0 {
|
||||
q.Set("per_page", strconv.Itoa(params.PerPage))
|
||||
}
|
||||
if params.Query != "" {
|
||||
q.Set("query", params.Query)
|
||||
}
|
||||
if params.Sort != "" {
|
||||
q.Set("sort", params.Sort)
|
||||
}
|
||||
if params.Direction != "" {
|
||||
q.Set("direction", params.Direction)
|
||||
}
|
||||
if params.Filter != "" {
|
||||
q.Set("filter", params.Filter)
|
||||
}
|
||||
if params.FilterType != "" {
|
||||
q.Set("filter_type", params.FilterType)
|
||||
}
|
||||
|
||||
path := "/me/videos"
|
||||
if enc := q.Encode(); enc != "" {
|
||||
path += "?" + enc
|
||||
}
|
||||
|
||||
resp, err := c.doRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("failed to list videos: status %d, body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
var out ListVideosResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode list videos response: %w", err)
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateUpload(ctx context.Context, req *UploadRequest) (*UploadResponse, error) {
|
||||
resp, err := c.doRequest(ctx, http.MethodPost, "/me/videos", req)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ var AllPermissions = []domain.PermissionSeed{
|
|||
{Key: "analytics.dashboard", Name: "View Dashboard", Description: "View analytics dashboard", GroupName: "Analytics"},
|
||||
|
||||
// Vimeo
|
||||
{Key: "vimeo.videos.list", Name: "List Vimeo Videos", Description: "List videos in the Vimeo account", GroupName: "Vimeo"},
|
||||
{Key: "vimeo.videos.get", Name: "Get Vimeo Video", Description: "Get Vimeo video details", GroupName: "Vimeo"},
|
||||
{Key: "vimeo.videos.embed", Name: "Get Embed Code", Description: "Get Vimeo embed code", GroupName: "Vimeo"},
|
||||
{Key: "vimeo.videos.status", Name: "Get Transcode Status", Description: "Get Vimeo transcode status", GroupName: "Vimeo"},
|
||||
|
|
@ -380,7 +381,7 @@ var DefaultRolePermissions = map[string][]string{
|
|||
"analytics.dashboard",
|
||||
|
||||
// Vimeo
|
||||
"vimeo.videos.get", "vimeo.videos.embed", "vimeo.videos.status", "vimeo.videos.delete",
|
||||
"vimeo.videos.list", "vimeo.videos.get", "vimeo.videos.embed", "vimeo.videos.status", "vimeo.videos.delete",
|
||||
"vimeo.uploads.pull", "vimeo.uploads.tus",
|
||||
|
||||
// Team (full access)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,15 @@ type UploadResult struct {
|
|||
Status string
|
||||
}
|
||||
|
||||
// ListVideosPage is the service result for a paginated Vimeo library query.
|
||||
type ListVideosPage struct {
|
||||
Total int
|
||||
Page int
|
||||
PerPage int
|
||||
Paging vimeo.PagingLinks
|
||||
Videos []*VideoInfo
|
||||
}
|
||||
|
||||
func (s *Service) GetVideoInfo(ctx context.Context, videoID string) (*VideoInfo, error) {
|
||||
video, err := s.client.GetVideo(ctx, videoID)
|
||||
if err != nil {
|
||||
|
|
@ -52,6 +61,18 @@ func (s *Service) GetVideoInfo(ctx context.Context, videoID string) (*VideoInfo,
|
|||
return nil, fmt.Errorf("failed to get video: %w", err)
|
||||
}
|
||||
|
||||
return s.videoModelToInfo(video, videoID), nil
|
||||
}
|
||||
|
||||
func (s *Service) videoModelToInfo(video *vimeo.Video, fallbackID string) *VideoInfo {
|
||||
videoID := fallbackID
|
||||
if videoID == "" {
|
||||
videoID = vimeo.ExtractVideoID(video.URI)
|
||||
}
|
||||
if videoID == "" {
|
||||
videoID = vimeo.ExtractVideoID(video.Link)
|
||||
}
|
||||
|
||||
info := &VideoInfo{
|
||||
VimeoID: videoID,
|
||||
URI: video.URI,
|
||||
|
|
@ -66,7 +87,7 @@ func (s *Service) GetVideoInfo(ctx context.Context, videoID string) (*VideoInfo,
|
|||
|
||||
if video.PlayerEmbedURL != "" {
|
||||
info.EmbedURL = video.PlayerEmbedURL
|
||||
} else {
|
||||
} else if videoID != "" {
|
||||
info.EmbedURL = vimeo.GenerateEmbedURL(videoID, nil)
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +103,28 @@ func (s *Service) GetVideoInfo(ctx context.Context, videoID string) (*VideoInfo,
|
|||
info.TranscodeStatus = video.Transcode.Status
|
||||
}
|
||||
|
||||
return info, nil
|
||||
return info
|
||||
}
|
||||
|
||||
func (s *Service) ListVideos(ctx context.Context, params vimeo.ListVideosParams) (*ListVideosPage, error) {
|
||||
raw, err := s.client.ListMyVideos(ctx, params)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to list Vimeo videos", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to list videos: %w", err)
|
||||
}
|
||||
|
||||
out := &ListVideosPage{
|
||||
Total: raw.Total,
|
||||
Page: raw.Page,
|
||||
PerPage: raw.PerPage,
|
||||
Paging: raw.Paging,
|
||||
Videos: make([]*VideoInfo, 0, len(raw.Data)),
|
||||
}
|
||||
for i := range raw.Data {
|
||||
out.Videos = append(out.Videos, s.videoModelToInfo(&raw.Data[i], ""))
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *Service) CreatePullUpload(ctx context.Context, name, description, sourceURL string, fileSize int64) (*UploadResult, error) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package handlers
|
|||
import (
|
||||
"Yimaru-Backend/internal/domain"
|
||||
"Yimaru-Backend/internal/pkgs/vimeo"
|
||||
vimeoservice "Yimaru-Backend/internal/services/vimeo"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
|
|
@ -62,6 +63,124 @@ type VimeoEmbedResponse struct {
|
|||
EmbedHTML string `json:"embed_html"`
|
||||
}
|
||||
|
||||
type VimeoVideosListMetadata struct {
|
||||
domain.Pagination
|
||||
Next string `json:"next,omitempty"`
|
||||
Previous string `json:"previous,omitempty"`
|
||||
First string `json:"first,omitempty"`
|
||||
Last string `json:"last,omitempty"`
|
||||
}
|
||||
|
||||
func vimeoVideoInfoToResponse(info *vimeoservice.VideoInfo) VimeoVideoResponse {
|
||||
return 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,
|
||||
}
|
||||
}
|
||||
|
||||
// ListVimeoVideos godoc
|
||||
// @Summary List videos stored in the Vimeo account
|
||||
// @Description Returns a paginated list of videos for the Vimeo API token (GET https://api.vimeo.com/me/videos)
|
||||
// @Tags Vimeo
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number (starts at 1)" default(1)
|
||||
// @Param per_page query int false "Page size (Vimeo max 100)" default(25)
|
||||
// @Param query query string false "Search query"
|
||||
// @Param sort query string false "Sort field (e.g. date, alphabetical, plays, likes, comments, duration, relevance)"
|
||||
// @Param direction query string false "asc or desc"
|
||||
// @Param filter query string false "Vimeo filter (e.g. embeddable, playable)"
|
||||
// @Param filter_type query string false "Vimeo filter_type when using filter"
|
||||
// @Success 200 {object} domain.Response{data=[]handlers.VimeoVideoResponse,metadata=handlers.VimeoVideosListMetadata}
|
||||
// @Failure 503 {object} domain.ErrorResponse
|
||||
// @Failure 500 {object} domain.ErrorResponse
|
||||
// @Router /api/v1/vimeo/videos [get]
|
||||
func (h *Handler) ListVimeoVideos(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",
|
||||
})
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.Query("page", "1"))
|
||||
perPage, _ := strconv.Atoi(c.Query("per_page", "25"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if perPage < 1 {
|
||||
perPage = 25
|
||||
}
|
||||
if perPage > 100 {
|
||||
perPage = 100
|
||||
}
|
||||
|
||||
params := vimeo.ListVideosParams{
|
||||
Page: page,
|
||||
PerPage: perPage,
|
||||
Query: c.Query("query"),
|
||||
Sort: c.Query("sort"),
|
||||
Direction: c.Query("direction"),
|
||||
Filter: c.Query("filter"),
|
||||
FilterType: c.Query("filter_type"),
|
||||
}
|
||||
|
||||
pageResult, err := h.vimeoSvc.ListVideos(c.Context(), params)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to list Vimeo videos",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
items := make([]VimeoVideoResponse, 0, len(pageResult.Videos))
|
||||
for _, info := range pageResult.Videos {
|
||||
if info == nil {
|
||||
continue
|
||||
}
|
||||
items = append(items, vimeoVideoInfoToResponse(info))
|
||||
}
|
||||
|
||||
totalPages := 0
|
||||
if pageResult.PerPage > 0 && pageResult.Total > 0 {
|
||||
totalPages = (pageResult.Total + pageResult.PerPage - 1) / pageResult.PerPage
|
||||
}
|
||||
currentPage := pageResult.Page
|
||||
if currentPage < 1 {
|
||||
currentPage = page
|
||||
}
|
||||
|
||||
return c.JSON(domain.Response{
|
||||
Message: "Vimeo videos listed successfully",
|
||||
Data: items,
|
||||
MetaData: VimeoVideosListMetadata{
|
||||
Pagination: domain.Pagination{
|
||||
Total: pageResult.Total,
|
||||
TotalPages: totalPages,
|
||||
CurrentPage: currentPage,
|
||||
Limit: pageResult.PerPage,
|
||||
},
|
||||
Next: pageResult.Paging.Next,
|
||||
Previous: pageResult.Paging.Previous,
|
||||
First: pageResult.Paging.First,
|
||||
Last: pageResult.Paging.Last,
|
||||
},
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
}
|
||||
|
||||
// GetVimeoVideo godoc
|
||||
// @Summary Get video information from Vimeo
|
||||
// @Description Retrieves video details from Vimeo by video ID
|
||||
|
|
@ -99,21 +218,7 @@ func (h *Handler) GetVimeoVideo(c *fiber.Ctx) error {
|
|||
|
||||
return c.JSON(domain.Response{
|
||||
Message: "Video retrieved successfully",
|
||||
Data: 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,
|
||||
},
|
||||
Data: vimeoVideoInfoToResponse(info),
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
})
|
||||
|
|
@ -413,21 +518,7 @@ func (h *Handler) GetSampleVideo(c *fiber.Ctx) 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,
|
||||
},
|
||||
"video": vimeoVideoInfoToResponse(info),
|
||||
"iframe": iframe,
|
||||
},
|
||||
Success: true,
|
||||
|
|
|
|||
|
|
@ -357,6 +357,7 @@ func (a *App) initAppRoutes() {
|
|||
|
||||
// Vimeo
|
||||
vimeoGroup := groupV1.Group("/vimeo")
|
||||
vimeoGroup.Get("/videos", a.authMiddleware, a.RequirePermission("vimeo.videos.list"), h.ListVimeoVideos)
|
||||
vimeoGroup.Get("/videos/:video_id", a.authMiddleware, a.RequirePermission("vimeo.videos.get"), h.GetVimeoVideo)
|
||||
vimeoGroup.Get("/videos/:video_id/embed", a.authMiddleware, a.RequirePermission("vimeo.videos.embed"), h.GetEmbedCode)
|
||||
vimeoGroup.Get("/videos/:video_id/status", a.authMiddleware, a.RequirePermission("vimeo.videos.status"), h.GetTranscodeStatus)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user