Initdb: create database schemas, admin role, initial background map
All checks were successful
/ test (push) Successful in 32s

This commit is contained in:
phil 2024-12-20 11:45:44 +01:00
parent e44911db8e
commit 7354b9bab8
6 changed files with 76 additions and 47 deletions

View file

@ -2,6 +2,3 @@ FROM docker.io/postgis/postgis:17-3.5-alpine
ENV POSTGRES_USER gisaf ENV POSTGRES_USER gisaf
ENV POSTGRES_PASSWORD secret ENV POSTGRES_PASSWORD secret
# Overwrite standard postgis entrypoint
COPY ./database-container-entrypoint-postgis.sh /docker-entrypoint-initdb.d/10_postgis.sh

View file

@ -1,29 +0,0 @@
#!/bin/bash
set -e
# Perform all actions as $POSTGRES_USER
export PGUSER="$POSTGRES_USER"
# Create the 'template_postgis' template db
"${psql[@]}" <<-'EOSQL'
CREATE DATABASE template_postgis IS_TEMPLATE true;
EOSQL
# Load PostGIS into both template_database and $POSTGRES_DB
for DB in template_postgis "$POSTGRES_DB"; do
echo "Loading PostGIS extensions into $DB"
"${psql[@]}" --dbname="$DB" <<-'EOSQL'
CREATE EXTENSION IF NOT EXISTS postgis;
EOSQL
done
"${psql[@]}" --dbname="$DB" <<-'EOSQL'
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE SCHEMA gisaf;
CREATE SCHEMA gisaf_admin;
CREATE SCHEMA gisaf_map;
CREATE SCHEMA gisaf_survey;
CREATE SCHEMA raw_survey;
CREATE SCHEMA survey;
EOSQL

View file

@ -115,7 +115,7 @@ async def get_acls(
db_session: db_session, user: Annotated[User, Depends(get_current_active_user)] db_session: db_session, user: Annotated[User, Depends(get_current_active_user)]
) -> list[UserRoleLink]: ) -> list[UserRoleLink]:
"""New: ACLs returned as UserRoleLink""" """New: ACLs returned as UserRoleLink"""
if user is not None or not user.has_role("manager"): if user is None or not user.has_role("manager"):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED) raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
data = await db_session.exec(select(UserRoleLink)) data = await db_session.exec(select(UserRoleLink))
return data.all() # type: ignore[return-value] return data.all() # type: ignore[return-value]

View file

@ -5,7 +5,7 @@ from asyncio import sleep
import logging import logging
from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import create_engine from sqlalchemy import create_engine, text
from sqlalchemy.orm import joinedload, QueryableAttribute from sqlalchemy.orm import joinedload, QueryableAttribute
from sqlalchemy.sql.selectable import Select from sqlalchemy.sql.selectable import Select
from sqlmodel import SQLModel, select, func, col from sqlmodel import SQLModel, select, func, col
@ -163,6 +163,8 @@ async def create_db(drop=False):
attempts = CREATE_DB_TIMEOUT attempts = CREATE_DB_TIMEOUT
async def try_once(): async def try_once():
async with engine.begin() as conn:
await create_schemas(conn)
async with engine.begin() as conn: async with engine.begin() as conn:
if drop: if drop:
await conn.run_sync(SQLModel.metadata.drop_all) await conn.run_sync(SQLModel.metadata.drop_all)
@ -199,14 +201,30 @@ async def is_fresh_install() -> bool:
return nb_users == 0 return nb_users == 0
async def create_schemas(conn):
"""Create schemas and extensions"""
raw_sql = [
"CREATE EXTENSION IF NOT EXISTS hstore",
"CREATE SCHEMA IF NOT EXISTS gisaf",
"CREATE SCHEMA IF NOT EXISTS gisaf_admin",
"CREATE SCHEMA IF NOT EXISTS gisaf_map",
"CREATE SCHEMA IF NOT EXISTS gisaf_survey",
"CREATE SCHEMA IF NOT EXISTS raw_survey",
"CREATE SCHEMA IF NOT EXISTS survey",
]
for rs in raw_sql:
await conn.execute(text(rs))
async def populate_init_db(): async def populate_init_db():
"""Populate the database for a fresh install""" """Populate the database for a fresh install"""
from sqlalchemy import text from gisaf.security import create_user, create_role, add_user_role
from gisaf.security import create_user # , add_role, add_user_role from gisaf.models.map_bases import BaseStyle
logger.info("Populating initial database") logger.info("Populating initial database")
async with db_session() as session: async with db_session() as session:
user = await create_user( user = await create_user(
session=session, session=session,
username="admin", username="admin",
@ -215,10 +233,15 @@ async def populate_init_db():
email="root@localhost.localdomain", email="root@localhost.localdomain",
active=True, active=True,
) )
assert user is not None role = await create_role(
# role = await add_role(role_id="admin") session, name="admin", description="Initial admin user"
# await add_user_role(user.username, role.name) )
# for initial in initials: await session.refresh(user)
# await session.execute(text(initial)) await session.refresh(role)
# logger.debug(f"Added map style {initial}") await add_user_role(session, user, role)
# await session.commit() openFreeMap = BaseStyle(
name="OpenFreeMap",
static_url="https://tiles.openfreemap.org/styles/liberty",
)
session.add(openFreeMap)
await session.commit()

View file

@ -19,9 +19,9 @@ class BaseStyle(Model, table=True):
id: int | None = Field(primary_key=True, default=None) id: int | None = Field(primary_key=True, default=None)
name: str name: str
style: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore style: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True), default=None)
mbtiles: str = Field(sa_type=String(50)) # type: ignore mbtiles: str | None = Field(sa_type=String(50), default=None)
static_tiles_url: str static_url: str | None = Field(sa_type=String(250), default=None)
enabled: bool = True enabled: bool = True
def __repr__(self): def __repr__(self):

View file

@ -15,7 +15,7 @@ from sqlalchemy.orm import selectinload
from gisaf.config import conf from gisaf.config import conf
from gisaf.database import db_session from gisaf.database import db_session
from gisaf.models.authentication import User, UserRead from gisaf.models.authentication import User, UserRead, Role, UserRoleLink
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -97,6 +97,38 @@ async def create_user(
return user_in_db return user_in_db
async def create_role(
session: AsyncSession,
name: str,
description: str | None = None,
):
role_in_db = await get_role(session, name)
if role_in_db is None:
role = Role(
name=name,
description=description,
)
session.add(role)
await session.commit()
return role
else:
role_in_db.description = description
await session.commit()
return role_in_db
async def add_user_role(session: AsyncSession, user: User, role: Role) -> User | None:
query = select(UserRoleLink).where(
UserRoleLink.user_id == user.id, UserRoleLink.role_id == role.id
)
data = await session.exec(query)
user_role = data.one_or_none()
if user_role is None:
user_role = UserRoleLink(user_id=user.id, role_id=role.id)
session.add(user_role)
await session.commit()
async def get_user(session: AsyncSession, username: str) -> User | None: async def get_user(session: AsyncSession, username: str) -> User | None:
query = ( query = (
select(User).where(User.username == username).options(selectinload(User.roles)) select(User).where(User.username == username).options(selectinload(User.roles))
@ -105,6 +137,12 @@ async def get_user(session: AsyncSession, username: str) -> User | None:
return data.one_or_none() return data.one_or_none()
async def get_role(session: AsyncSession, name: str) -> Role | None:
query = select(Role).where(Role.name == name)
data = await session.exec(query)
return data.one_or_none()
def verify_password(user: User, plain_password): def verify_password(user: User, plain_password):
try: try:
return pwd_context.verify(plain_password, user.password) return pwd_context.verify(plain_password, user.password)