Compare commits

...
10 Commits
Author SHA1 Message Date
shmyga 160e1f9156 ci(version): 0.10.5
release / release (push) Successful in 3m53s
test / test (push) Successful in 52s
2026-09-30 20:57:43 +03:00
shmyga ad8f29c5ef feat(easel): add api link button to views
test / test (push) Successful in 59s
2026-09-30 20:56:17 +03:00
shmyga 4fc1c8c8e5 docs(readme): add test status badge
test / test (push) Successful in 56s
2026-09-30 19:43:27 +03:00
shmyga e414a93242 ci(action): add deploy action
release / release (push) Successful in 46s
test / test (push) Successful in 44s
2026-09-30 19:23:07 +03:00
shmyga 714fd1567c ci(action): use project-manager action
release / release (push) Successful in 39s
test / test (push) Successful in 49s
2026-09-30 16:03:23 +03:00
shmyga 1d1ba69cc8 ci(version): 0.10.4
release / release (push) Successful in 8m42s
test / test (push) Successful in 56s
2026-09-29 22:54:33 +03:00
shmyga f35d3f8cb8 build(docker): fix playwright version 2026-09-29 22:42:41 +03:00
shmyga b0187506fd ci(action): add setup composite action
test / test (push) Successful in 35s
2026-09-28 19:10:52 +03:00
shmyga beafed8faf ci(version): 0.10.3
build / build (push) Successful in 3m17s
test / test (push) Successful in 42s
2026-09-28 15:28:43 +03:00
shmyga 52315cc8bd fix(weather): add query params to days tag route redirect 2026-09-28 15:28:18 +03:00
16 changed files with 117 additions and 112 deletions
-42
View File
@@ -1,42 +0,0 @@
name: build
on:
push:
tags: ["*"]
jobs:
build:
runs-on: ubuntu-latest
steps:
# repo
- name: Check out repository
uses: actions/checkout@v7
# python
- name: Set up python
id: setup-python
uses: actions/setup-python@v7
with:
python-version: "3.14"
cache: "pip"
# project-manager
- name: Install Project Manager
run: |
pip install project-manager \
--index-url https://git.shmyga.ru/api/packages/InfernalGames/pypi/simple/ \
--extra-index-url https://pypi.org/simple
# setup
- name: Setup config
run: pm setup:config -vv
# build
- name: Build docker
run: pm build:docker -vv
# login docker
- name: Login to Docker Hub
uses: docker/login-action@v4
with:
registry: git.shmyga.ru
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# pubish
- name: Publish docker
run: pm publish:docker -vv
+22
View File
@@ -0,0 +1,22 @@
name: deploy
on:
workflow_run:
workflows: [release]
types: [completed]
jobs:
deploy:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USERNAME }}
key: ${{ secrets.DEPLOY_KEY }}
passphrase: ${{ secrets.DEPLOY_PASSPHRASE }}
script: |
cd /opt/gallery
docker compose pull
docker compose up -d
+20
View File
@@ -0,0 +1,20 @@
name: release
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: https://git.shmyga.ru/InfernalGames/project-manager@master
- run: pm build:docker -vv
- uses: docker/login-action@v4
with:
registry: git.shmyga.ru
username: ${{ vars.PUBLISH_USERNAME }}
password: ${{ secrets.PUBLISH_TOKEN }}
- run: pm publish:docker -vv
+5 -41
View File
@@ -6,45 +6,9 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# repo - uses: actions/checkout@v7
- name: Check out repository - uses: https://git.shmyga.ru/InfernalGames/project-manager@master
uses: actions/checkout@v7
# python
- name: Set up python
id: setup-python
uses: actions/setup-python@v7
with: with:
python-version: "3.14" setup-python: true
# poetry - run: pm lint -vv
- name: Load cached Poetry installation - run: pm test -vv
id: cached-poetry
uses: actions/cache@v5
with:
path: ~/.local
key: poetry-1 # increment to reset cache
- name: Install Poetry
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
installer-parallel: true
# venv
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v5
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: ~/.local/bin/poetry install --no-interaction --no-root
- name: Install project
run: ~/.local/bin/poetry install --no-interaction
# test
- name: Run tests
run: |
source .venv/bin/activate
coverage run -m pytest tests/
coverage report
+1 -1
View File
@@ -27,7 +27,7 @@ FROM python:3.14-slim
ENV PLAYWRIGHT_BROWSERS_PATH="/opt/playwright-browsers" ENV PLAYWRIGHT_BROWSERS_PATH="/opt/playwright-browsers"
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \
pip install playwright && \ pip install playwright==1.62 && \
playwright install --with-deps chromium playwright install --with-deps chromium
ENV PATH="/app/.venv/bin:$PATH" ENV PATH="/app/.venv/bin:$PATH"
+2
View File
@@ -1,5 +1,7 @@
# API Gallery # API Gallery
[![test](https://git.shmyga.ru/InfernalGames/gallery/actions/workflows/test.yaml/badge.svg?branch=master)](https://git.shmyga.ru/InfernalGames/gallery/actions?workflow=test.yaml)
Currently implemented APIs: Currently implemented APIs:
- Weather forecast - Weather forecast
+23 -11
View File
@@ -1,4 +1,3 @@
import datetime
from pathlib import Path from pathlib import Path
from urllib import parse from urllib import parse
@@ -13,9 +12,7 @@ from gallery.easel.depends.schedule import (
ScheduleApiDepends, ScheduleApiDepends,
ScheduleGroupChannelsDepends, ScheduleGroupChannelsDepends,
) )
from gallery.sketch.bundle import ApiBundle
from gallery.sketch.schedule.api import ScheduleApi from gallery.sketch.schedule.api import ScheduleApi
from gallery.sketch.schedule.model import Schedule
from ..common.utils.template import build_templates from ..common.utils.template import build_templates
from .filters import timedelta_format from .filters import timedelta_format
@@ -52,12 +49,6 @@ async def get_schedule_index(request: AppRequest, provider: str | None = None, q
) )
async def _get_group_schedule(api: ApiBundle, channels: list[tuple[str, str]], date: datetime.date) -> list[Schedule]:
return [
await api.get_api(ScheduleApi, provider).get_schedule(channel_id, date) for provider, channel_id in channels
]
@router.get("/group/{group_id}", response_class=RedirectResponse) @router.get("/group/{group_id}", response_class=RedirectResponse)
async def get_group_default(group_id: str): async def get_group_default(group_id: str):
return RedirectResponse(f"{parse.quote(group_id)}/today") return RedirectResponse(f"{parse.quote(group_id)}/today")
@@ -72,7 +63,16 @@ async def get_group(request: AppRequest, channels: ScheduleGroupChannelsDepends,
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="group.html", name="group.html",
context={"responses": responses}, context={
"responses": responses,
"api_route": "/".join(
[
"/api/schedule/group",
request.path_params["group_id"],
request.path_params["date"],
]
),
},
) )
@@ -87,5 +87,17 @@ async def get_channel(request: AppRequest, schedule_api: ScheduleApiDepends, cha
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="channel.html", name="channel.html",
context={"response": response}, context={
"response": response,
"api_route": (
"/".join(
[
"/api/schedule",
request.path_params["provider"],
request.path_params["channel"],
request.path_params["date"],
]
)
),
},
) )
@@ -10,6 +10,9 @@ locale=request.state.language)}}
{% block header %} {% block header %}
<app-link href="/schedule" <app-link href="/schedule"
icon="tv">{{_("TV program")}}</app-link> icon="tv">{{_("TV program")}}</app-link>
<app-link target="_blank"
href="{{ api_route }}"
icon="filetype-json"></app-link>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@@ -44,7 +47,8 @@ locale=request.state.language)}}
{% for value in response.values %} {% for value in response.values %}
<tr class="{{'d-none' if (live and not value.live) else 'table-success' if (not live and value.live) else ''}}"> <tr class="{{'d-none' if (live and not value.live) else 'table-success' if (not live and value.live) else ''}}">
<td> <td>
<span>{{value.start.strftime('%H:%M')}}</span>&nbsp;<span class="small ms-1">({{(value.end - value.start) | timedelta_format}})</span> <span>{{value.start.strftime('%H:%M')}}</span>&nbsp;<span class="small ms-1">({{(value.end - value.start) |
timedelta_format}})</span>
</td> </td>
<td>{{value.label}}</td> <td>{{value.label}}</td>
</tr> </tr>
@@ -9,6 +9,9 @@
{% block header %} {% block header %}
<app-link href="/schedule" <app-link href="/schedule"
icon="tv">{{_("TV program")}}</app-link> icon="tv">{{_("TV program")}}</app-link>
<app-link target="_blank"
href="{{ api_route }}"
icon="filetype-json"></app-link>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
+16 -3
View File
@@ -3,6 +3,7 @@ from typing import Literal
from fastapi import APIRouter from fastapi import APIRouter
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import HTMLResponse, RedirectResponse
from starlette.datastructures import URL
from gallery.easel.core import AppRequest from gallery.easel.core import AppRequest
from gallery.easel.depends.api import api_resolver from gallery.easel.depends.api import api_resolver
@@ -36,7 +37,18 @@ def build_weather_response(request: AppRequest, response: WeatherResponse):
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="weather.html", name="weather.html",
context={"response": response}, context={
"response": response,
"api_route": "/".join(
[
"/api/weather",
request.path_params["provider"],
request.path_params["location"],
response.period,
request.path_params.get("date") or request.path_params.get("days") or "",
]
),
},
) )
@@ -70,8 +82,9 @@ async def get_weather_days(request: AppRequest, weather_api: WeatherApiDepends,
# TODO: tag mode route deprecated, used in tizen app # TODO: tag mode route deprecated, used in tizen app
@router.get("/{provider}/{location}/tag/days-{days}", response_class=RedirectResponse) @router.get("/{provider}/{location}/tag/days-{days}", response_class=RedirectResponse)
async def get_tag_days(days: int): async def get_tag_days(request: AppRequest, days: int):
return RedirectResponse(f"../days/{days}") url = URL(path=f"../days/{days}", query=request.url.query)
return RedirectResponse(url)
# TODO: tag mode route deprecated, used in tizen app # TODO: tag mode route deprecated, used in tizen app
@@ -7,6 +7,9 @@ locale=request.state.language)}}{% endblock %}
{% block header %} {% block header %}
<app-link href="/weather" <app-link href="/weather"
icon="brightness-high">{{_("Weather")}}</app-link> icon="brightness-high">{{_("Weather")}}</app-link>
<app-link target="_blank"
href="{{ api_route }}"
icon="filetype-json"></app-link>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
+2 -1
View File
@@ -29,6 +29,7 @@ class YandexTVApi(ScheduleApi):
return self.channel_parser.parse(data) return self.channel_parser.parse(data)
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule: async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
endpoint = f"{self.lr}/channel/{channel_id}?date={date:%Y-%m-%d}" channel_path_part = "channel" if "-" in channel_id else "channels"
endpoint = f"{self.lr}/{channel_path_part}/{channel_id}?date={date:%Y-%m-%d}"
data = await self.source.request(endpoint) data = await self.source.request(endpoint)
return self.schedule_parser.parse(ScheduleData(date, channel_id, data)) return self.schedule_parser.parse(ScheduleData(date, channel_id, data))
+9 -9
View File
@@ -1,16 +1,16 @@
[project] [project]
name = "gallery" name = "gallery"
description = "" description = ""
version = "0.10.2" version = "0.10.5"
readme = "README.md" readme = "README.md"
authors = [{ name = "shmyga", email = "shmyga.z@gmail.com" }] authors = [{ name = "shmyga", email = "shmyga.z@gmail.com" }]
requires-python = '>=3.14,<4.0' requires-python = ">=3.14,<4.0"
dependencies = [ dependencies = [
'aiohttp (>=3.14,<4.0)', "aiohttp (>=3.14,<4.0)",
'beautifulsoup4 (>=4.15,<5.0)', "beautifulsoup4 (>=4.15,<5.0)",
'dateparser (>=1.4,<2.0)', "dateparser (>=1.4,<2.0)",
'pydantic (>=2.13,<3.0)', "pydantic (>=2.13,<3.0)",
'aiocache[redis] (>=0.12,<0.13)', "aiocache[redis] (>=0.12,<0.13)",
"playwright (>=1.62.0,<2.0.0)", "playwright (>=1.62.0,<2.0.0)",
] ]
@@ -30,10 +30,10 @@ gallery = "gallery.main:run"
[tool.poetry] [tool.poetry]
packages = [{ include = "gallery" }] packages = [{ include = "gallery" }]
requires-poetry = '>=2.0.0,<3.0.0' requires-poetry = ">=2.0.0,<3.0.0"
[build-system] [build-system]
requires = ['poetry-core (>=2.0.0,<3.0.0)'] requires = ["poetry-core (>=2.0.0,<3.0.0)"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.black] [tool.black]
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "gallery", "name": "gallery",
"version": "0.10.2", "version": "0.10.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "gallery", "name": "gallery",
"version": "0.10.2", "version": "0.10.5",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "gallery", "name": "gallery",
"version": "0.10.2", "version": "0.10.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "webpack --mode production", "build": "webpack --mode production",
+3
View File
@@ -48,4 +48,7 @@
&.bi-cast { &.bi-cast {
mask-image: url(bootstrap-icons/icons/cast.svg); mask-image: url(bootstrap-icons/icons/cast.svg);
} }
&.bi-filetype-json {
mask-image: url(bootstrap-icons/icons/filetype-json.svg);
}
} }