sync
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""add canonical waterbody provenance fields
|
||||
|
||||
Revision ID: 0017
|
||||
Revises: 0016
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "0017"
|
||||
down_revision = "0016"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("waterbody", sa.Column("source_system", sa.String(length=50), nullable=True))
|
||||
op.add_column("waterbody", sa.Column("source_external_id", sa.String(length=200), nullable=True))
|
||||
op.add_column("waterbody", sa.Column("source_url", sa.Text(), nullable=True))
|
||||
op.add_column("waterbody", sa.Column("description", sa.Text(), nullable=True))
|
||||
op.add_column("waterbody", sa.Column("source_checked_at", sa.DateTime(timezone=True), nullable=True))
|
||||
op.create_index(
|
||||
"uq_waterbody_source_identity",
|
||||
"waterbody",
|
||||
["source_system", "source_external_id"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("uq_waterbody_source_identity", table_name="waterbody")
|
||||
op.drop_column("waterbody", "source_checked_at")
|
||||
op.drop_column("waterbody", "description")
|
||||
op.drop_column("waterbody", "source_url")
|
||||
op.drop_column("waterbody", "source_external_id")
|
||||
op.drop_column("waterbody", "source_system")
|
||||
@@ -0,0 +1,26 @@
|
||||
"""store source coordinate text and precision
|
||||
|
||||
Revision ID: 0018
|
||||
Revises: 0017
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "0018"
|
||||
down_revision = "0017"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("external_observation", sa.Column("coordinate_raw", sa.String(length=200), nullable=True))
|
||||
op.add_column("external_observation", sa.Column("coordinate_precision", sa.String(length=20), nullable=True))
|
||||
op.execute("UPDATE external_observation SET coordinate_precision = CASE WHEN x IS NOT NULL AND y IS NOT NULL THEN 'exact' ELSE 'missing' END")
|
||||
op.alter_column("external_observation", "coordinate_precision", nullable=False, server_default="missing")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("external_observation", "coordinate_precision")
|
||||
op.drop_column("external_observation", "coordinate_raw")
|
||||
@@ -0,0 +1,22 @@
|
||||
"""store canonical waterbody fish count
|
||||
|
||||
Revision ID: 0019
|
||||
Revises: 0018
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "0019"
|
||||
down_revision = "0018"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("waterbody", sa.Column("fish_species_count", sa.Integer(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("waterbody", "fish_species_count")
|
||||
@@ -0,0 +1,24 @@
|
||||
"""store canonical waterbody detail facts
|
||||
|
||||
Revision ID: 0020
|
||||
Revises: 0019
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "0020"
|
||||
down_revision = "0019"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
for name in ("source_aliases", "source_fish_species", "source_image_urls", "source_point_urls"):
|
||||
op.add_column("waterbody", sa.Column(name, sa.JSON(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for name in ("source_point_urls", "source_image_urls", "source_fish_species", "source_aliases"):
|
||||
op.drop_column("waterbody", name)
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
import { pageHref, pageWindow } from "../lib/pagination";
|
||||
|
||||
interface Props {
|
||||
path: string;
|
||||
params: URLSearchParams;
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
anchor?: string;
|
||||
itemLabel?: string;
|
||||
}
|
||||
|
||||
const { path, params, total, limit, offset, anchor = "", itemLabel = "записей" } = Astro.props;
|
||||
const state = pageWindow(total, limit, offset);
|
||||
---
|
||||
{total > limit && <nav class="pagination" aria-label="Пагинация">
|
||||
<span>{state.start}–{state.end} из {total} {itemLabel} · страница {state.page} из {state.pages}</span>
|
||||
<div>
|
||||
{state.previousOffset !== null
|
||||
? <a data-action="secondary" rel="prev" href={pageHref(path, params, state.previousOffset, anchor)}>← Предыдущая</a>
|
||||
: <span class="pagination__disabled" aria-disabled="true">← Предыдущая</span>}
|
||||
{state.nextOffset !== null
|
||||
? <a data-action="secondary" rel="next" href={pageHref(path, params, state.nextOffset, anchor)}>Следующая →</a>
|
||||
: <span class="pagination__disabled" aria-disabled="true">Следующая →</span>}
|
||||
</div>
|
||||
</nav>}
|
||||
@@ -0,0 +1,31 @@
|
||||
export type PageWindow = {
|
||||
start: number;
|
||||
end: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
previousOffset: number | null;
|
||||
nextOffset: number | null;
|
||||
};
|
||||
|
||||
export const pageWindow = (total: number, limit: number, offset: number): PageWindow => {
|
||||
const safeTotal = Math.max(0, Math.trunc(total));
|
||||
const safeLimit = Math.max(1, Math.trunc(limit));
|
||||
const safeOffset = Math.max(0, Math.trunc(offset));
|
||||
const end = Math.min(safeOffset + safeLimit, safeTotal);
|
||||
return {
|
||||
start: safeTotal > 0 && safeOffset < safeTotal ? safeOffset + 1 : 0,
|
||||
end,
|
||||
page: Math.floor(safeOffset / safeLimit) + 1,
|
||||
pages: Math.max(1, Math.ceil(safeTotal / safeLimit)),
|
||||
previousOffset: safeOffset > 0 ? Math.max(0, safeOffset - safeLimit) : null,
|
||||
nextOffset: end < safeTotal ? safeOffset + safeLimit : null,
|
||||
};
|
||||
};
|
||||
|
||||
export const pageHref = (path: string, search: URLSearchParams, offset: number, anchor = "") => {
|
||||
const params = new URLSearchParams(search);
|
||||
if (offset > 0) params.set("offset", String(offset));
|
||||
else params.delete("offset");
|
||||
const query = params.toString();
|
||||
return `${path}${query ? `?${query}` : ""}${anchor}`;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
.pagination{width:min(1360px,calc(100% - 64px));margin:22px auto 48px;display:flex;align-items:center;justify-content:space-between;gap:16px;color:var(--text-secondary);font-size:13px}.pagination>div{display:flex;gap:10px}.pagination a,.pagination__disabled{display:inline-flex;align-items:center;min-height:42px;padding:10px 15px;border:1px solid var(--border-strong);border-radius:10px;background:var(--surface-elevated);color:var(--text-secondary);text-decoration:none}.pagination a:hover{background:var(--surface-raised);color:var(--text-primary)}.pagination__disabled{opacity:.5}@media(max-width:720px){.pagination{width:calc(100% - 28px);align-items:stretch;flex-direction:column}.pagination>div{display:grid;grid-template-columns:1fr 1fr}.pagination a,.pagination__disabled{justify-content:center;text-align:center}}
|
||||
Reference in New Issue
Block a user