From 83863f4e96e56d4e565f87f8c26c582e1ffddb31 Mon Sep 17 00:00:00 2001 From: Damian Adil Date: Wed, 17 Apr 2024 21:16:38 +0100 Subject: [PATCH 1/7] Add readme and new developer documentation --- README.md | 26 ++++++++++++++++++++-- docs/01_dev_environment.md | 34 +++++++++++++++++++++++++++++ docs/02_creating_your_first_user.md | 12 ++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 docs/01_dev_environment.md create mode 100644 docs/02_creating_your_first_user.md diff --git a/README.md b/README.md index eb853eb..edd517a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# hackspace-mgmt -Hackspace management portal +# Hackspace Portal + +Welcome! + +This repository contains the source code for the Hackspace Portal: a small website used to keep track of everything members are up to in the hackspace. + +Whether you're a seasoned hacker, a novice tinkerer, or simply curious about the world of making, this portal is your gateway to a vibrant community of like-minded individuals (nice intro ChatGPT). + +## Purpose of the Portal + +The portal serves as a point of administration for all members. It is commonly used for: + +- **Membership Administration**: Enrol members, manage personal details, forum accounts and references to payment information. +- **Access Cards**: Associate access cards with members and revoke them remotely. +- **Inductions**: Pass a test to gain access to some of the more scary machines, then unlock them with your card. +- **Storage Labels**: Track short term storage labels. + +## Getting Started + +See the `docs` folder for some guidance on setting up your dev environment and creating your first user. + +## Contributing + +We welcome contributions from all members of the community! Whether you're a seasoned developer or just getting started, there are plenty of ways to get involved. If you have ideas for new features, encounter any bugs, or simply want to help improve the platform, we'd love to hear from you. diff --git a/docs/01_dev_environment.md b/docs/01_dev_environment.md new file mode 100644 index 0000000..0832f6f --- /dev/null +++ b/docs/01_dev_environment.md @@ -0,0 +1,34 @@ +# Dev Environment + +This is a somewhat straightforward python Flask app, backed by a Postgres database. + +The repository has a dev container configured which you can use if you like. + +Requiments: +- Python 3.9+ +- PostgreSQL 14+ - installed as part of the devcontainer if you are using it. +- Some ability to run Postgres queries directly - pgAdmin is a good GUI option, while `psql` is a good CLI - both are bundled with Postgres +- Git +- Docker/podman/your container orchestration tool of choice (if using dev containers). + +Most of us use VsCode as a lightweight IDE. + +### Database Setup + +1. Connect to the database using `psql postgres` or using pgAdmin. +2. Create a database called `hackspace`. In psql, you can run the query `CREATE DATABASE hackspace;` (don't forget the semi-colon!). +3. Under the `hackspace-mgmt/migration` folder is a bunch of SQL scripts. Run these, in order, against the new hackspace database. In pgAdmin, you would right click on the database and open the `Query` tool. Then copy-paste in the contents of each file and run them one-by-one. + +If you had to change the username, then you'll want to create a postgres user. You can do this by right-clicking the server and then _Create->Login/Group role_. Name the role `postgres`, then on the _Priveleges_ tab, enable _Can Login_ and _Superuser_ (this isn't recommended for production, but fine for development). + +### Webserver Setup + +In a terminal, navigate to the `hackspace-mgmt` folder and create a virtual environment with `python3 -m venv .venv`. This environment can then be activated with `source .venv/bin/activate` or `.venv/Scripts/activate.ps1` depending on which OS/terminal you are using. + +Update pip with `python -m pip install --upgrade pip`. + +Install the requirements with `pip install -r requirements.txt`. + +You should now be able to run the server with `flask --app hackspace_mgmt:create_app --debug run`. + +Navigate to `http://127.0.0.1:5000/admin/` and you should be able to see a bare admin page! \ No newline at end of file diff --git a/docs/02_creating_your_first_user.md b/docs/02_creating_your_first_user.md new file mode 100644 index 0000000..ab7070c --- /dev/null +++ b/docs/02_creating_your_first_user.md @@ -0,0 +1,12 @@ +# Creating Your First User + +In order to use the member facing page of the server, we will need to create a test user we can login with. + +1. Create a test card. From the `/admin` page, navigate to _Membership->Card_, then click _Create_. Enter 0 as the serial for now. This is an integer, but everything else uses the hexadecimal representation. Enter 123 for the _Number On Front_, leave everything else as it is and click _Save_. +2. Create a test member. From the `/admin` page, navigate to _Membership->Member_, then click _Create_. Enter anything you want into the name and email fields. In the _Cards_ field, type the number 123 then select the entry that appears. Click _Save_ to finish. + +## Logging In + +You should now be able to navigate to `http://127.0.0.1:5000`. This will show the login page that's displayed on the member portal in the Hackspace. + +Behind the scenes, this page listens for keyboard input and will log you in when a valid card serial is "typed" in. To login with the test card, type _0 then Enter_. You will have to be quick as there is a 0.5s timeout after the page loads! \ No newline at end of file From 3d5c6a4cb7e71381afe99ec54c2077c255b84511 Mon Sep 17 00:00:00 2001 From: Damian Adil Date: Wed, 17 Apr 2024 21:17:01 +0100 Subject: [PATCH 2/7] Set port forwarding for postgres in devcontainer.json --- .devcontainer/devcontainer.json | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7642a44..b50fe9d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,24 +1,7 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/postgres { "name": "Python 3 & PostgreSQL", "dockerComposeFile": "docker-compose.yml", "service": "app", - "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}" - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // This can be used to network with other containers or the host. - // "forwardPorts": [5000, 5432], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "pip install --user -r requirements.txt", - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "forwardPorts": [ 5432 ] } From 3e4f821b8417669e43628a15925607c69df12c9e Mon Sep 17 00:00:00 2001 From: Damian Adil Date: Sun, 28 Apr 2024 15:12:47 +0000 Subject: [PATCH 3/7] Build out the dev container to simplify development setup and flow --- .devcontainer/Dockerfile | 19 ++++---- .devcontainer/devcontainer.json | 9 +++- .devcontainer/docker-compose.yml | 11 ----- .gitignore | 3 +- .vscode/launch.json | 23 +++++++++ .vscode/tasks.json | 10 ++++ docs/01_dev_environment.md | 81 ++++++++++++++++++-------------- migrate.sh | 26 ++++++++++ 8 files changed, 124 insertions(+), 58 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 migrate.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index d86e312..ccab5a5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,14 +2,11 @@ FROM mcr.microsoft.com/devcontainers/python:0-3.11 ENV PYTHONUNBUFFERED 1 -# [Optional] If your requirements rarely change, uncomment this section to add them to the image. -# COPY requirements.txt /tmp/pip-tmp/ -# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ -# && rm -rf /tmp/pip-tmp - -# [Optional] Uncomment this section to install additional OS packages. -# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends - - - +COPY requirements.txt /tmp/pip-tmp/ +RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ + && rm -rf /tmp/pip-tmp + +# Installs the psql CLI. Note that to reach the DB from inside the devcontainer, +# we must run: psql -h localhost -U postgres (-d hackspace) +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends postgresql-client diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b50fe9d..8525edc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,5 +3,12 @@ "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", - "forwardPorts": [ 5432 ] + "forwardPorts": [ 5432 ], + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python" + ] + } + } } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index f2e9705..019c068 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -5,19 +5,11 @@ services: build: context: .. dockerfile: .devcontainer/Dockerfile - volumes: - ../..:/workspaces:cached - - # Overrides default command so things don't shut down after the process ends. command: sleep infinity - - # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. network_mode: service:db - # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) - db: image: postgres:latest restart: unless-stopped @@ -28,8 +20,5 @@ services: POSTGRES_DB: postgres POSTGRES_PASSWORD: postgres - # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. - # (Adding the "ports" property to this file will not forward from a Codespace.) - volumes: postgres-data: diff --git a/.gitignore b/.gitignore index 7260d15..945834e 100644 --- a/.gitignore +++ b/.gitignore @@ -129,4 +129,5 @@ dmypy.json .pyre/ -*.csv \ No newline at end of file +*.csv +last_migrated.txt diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e1bf8a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Flask", + "type": "debugpy", + "request": "launch", + "module": "flask", + "preLaunchTask": "Migrate", + "env": { + "FLASK_APP": "hackspace_mgmt/__init__.py", + "FLASK_DEBUG": "1" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "jinja": true, + "autoStartBrowser": true, + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..82bc16f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,10 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Migrate", + "type": "shell", + "command": ". ${workspaceFolder}/migrate.sh" + } + ] +} \ No newline at end of file diff --git a/docs/01_dev_environment.md b/docs/01_dev_environment.md index 0832f6f..0f448c4 100644 --- a/docs/01_dev_environment.md +++ b/docs/01_dev_environment.md @@ -1,34 +1,47 @@ -# Dev Environment - -This is a somewhat straightforward python Flask app, backed by a Postgres database. - -The repository has a dev container configured which you can use if you like. - -Requiments: -- Python 3.9+ -- PostgreSQL 14+ - installed as part of the devcontainer if you are using it. -- Some ability to run Postgres queries directly - pgAdmin is a good GUI option, while `psql` is a good CLI - both are bundled with Postgres -- Git -- Docker/podman/your container orchestration tool of choice (if using dev containers). - -Most of us use VsCode as a lightweight IDE. - -### Database Setup - -1. Connect to the database using `psql postgres` or using pgAdmin. -2. Create a database called `hackspace`. In psql, you can run the query `CREATE DATABASE hackspace;` (don't forget the semi-colon!). -3. Under the `hackspace-mgmt/migration` folder is a bunch of SQL scripts. Run these, in order, against the new hackspace database. In pgAdmin, you would right click on the database and open the `Query` tool. Then copy-paste in the contents of each file and run them one-by-one. - -If you had to change the username, then you'll want to create a postgres user. You can do this by right-clicking the server and then _Create->Login/Group role_. Name the role `postgres`, then on the _Priveleges_ tab, enable _Can Login_ and _Superuser_ (this isn't recommended for production, but fine for development). - -### Webserver Setup - -In a terminal, navigate to the `hackspace-mgmt` folder and create a virtual environment with `python3 -m venv .venv`. This environment can then be activated with `source .venv/bin/activate` or `.venv/Scripts/activate.ps1` depending on which OS/terminal you are using. - -Update pip with `python -m pip install --upgrade pip`. - -Install the requirements with `pip install -r requirements.txt`. - -You should now be able to run the server with `flask --app hackspace_mgmt:create_app --debug run`. - -Navigate to `http://127.0.0.1:5000/admin/` and you should be able to see a bare admin page! \ No newline at end of file +# Dev Environment + +This is a somewhat straightforward python Flask app, backed by a Postgres database. + +### With the Dev Container + +The repository has a dev container configured which you can use if you wish. This greatly simplifies the development environment setup process as it installs a relevant version of python, the app's dependencies, postgres server and database, some CLI tools and even some extensions into your an isolated workspace. + +1. Install a dev container capable IDE (we're using vscode by default), Git and Docker/podman/your container orchestration tool of choice. +2. Ensure you have the Dev Containers extension installed. +3. Clone and open the repository. Vscode will nudge you to open it in a dev container. +4. Click "Run" in the debug pane. + +Note: Both the dev container and postgres can be deleted and rebuilt as needed without losing your DB. They will automatically re-attach themselves to the `hackspace-mgmt_devcontainer_postgres-data` volume which is the important one not to lose as it contains the database itself. + +### Without the Dev Container + +
+ Expand + +#### Requiments: +- Python 3.9+ +- PostgreSQL 14+ +- Some ability to run Postgres queries directly - pgAdmin is a good GUI option, while `psql` is a good CLI. Both are bundled with Postgres. +- Git + +In a terminal, navigate to the `hackspace-mgmt` folder and create a virtual environment with `python3 -m venv .venv`. This environment can then be activated with `source .venv/bin/activate` or `.venv/Scripts/activate.ps1` depending on which OS/terminal you are using. + +Update pip with `python -m pip install --upgrade pip`. + +Install the requirements with `pip install -r requirements.txt`. + +You should now be able to run the server with `flask --app hackspace_mgmt:create_app --debug run` or by launching via the vscode debug pane. + +Navigate to `http://127.0.0.1:5000/admin/` and you should be able to see a bare admin page! + +
+ +### Database Setup + +The database is nominally configured by running each of the SQL scripts inside the `migration` folder sequentially. Anytime a new one is added, this will need to be applied before the app is started. + +If you're using a dev container, the `migrate.sh` script will be run prior to any debug session which does this automagically! + +Tooling-wise, you may wish to use `psql` - a CLI for interacting with the datbase directly. The dev container comes with it installed, but because it is running in a container, you need to use `psql -h localhost -U postgres (-d hackspace) etc` to connect. + +Separately, you may find it helpful to install `pgAdmin`. This is a free GUI tool for exploring Postgres, similar to SSMS. diff --git a/migrate.sh b/migrate.sh new file mode 100644 index 0000000..3625c5b --- /dev/null +++ b/migrate.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Create the hackspace DB if it doesn't already exist +psql -h localhost -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'hackspace'" | \ + grep -q 1 || psql -h localhost -U postgres -c "CREATE DATABASE hackspace" + +# Create the schema_version file if it doesn't exist +if ! [ -e last_migrated.txt ] ; then + echo -n "-1" > last_migrated.txt +fi + +last_migrated_file=$( cat last_migrated.txt ) +needs_migrating=false + +for file in migration/* +do + if [ "$last_migrated_file" = file ] || [ "$last_migrated_file" = "-1" ] ; then + needs_migrating=true + fi + + if [ "$needs_migrating" = true ] ; then + echo "Migrating $file" + psql -h localhost -U postgres -d hackspace -f "$file" + echo "$file" > last_migrated.txt + fi +done From ed36be4ef0122d4654bec2011080ee19c4bc47d7 Mon Sep 17 00:00:00 2001 From: Damian Adil Date: Wed, 17 Apr 2024 21:16:38 +0100 Subject: [PATCH 4/7] Add readme and new developer documentation --- README.md | 26 ++++++++++++++++++++-- docs/01_dev_environment.md | 34 +++++++++++++++++++++++++++++ docs/02_creating_your_first_user.md | 12 ++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 docs/01_dev_environment.md create mode 100644 docs/02_creating_your_first_user.md diff --git a/README.md b/README.md index eb853eb..edd517a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# hackspace-mgmt -Hackspace management portal +# Hackspace Portal + +Welcome! + +This repository contains the source code for the Hackspace Portal: a small website used to keep track of everything members are up to in the hackspace. + +Whether you're a seasoned hacker, a novice tinkerer, or simply curious about the world of making, this portal is your gateway to a vibrant community of like-minded individuals (nice intro ChatGPT). + +## Purpose of the Portal + +The portal serves as a point of administration for all members. It is commonly used for: + +- **Membership Administration**: Enrol members, manage personal details, forum accounts and references to payment information. +- **Access Cards**: Associate access cards with members and revoke them remotely. +- **Inductions**: Pass a test to gain access to some of the more scary machines, then unlock them with your card. +- **Storage Labels**: Track short term storage labels. + +## Getting Started + +See the `docs` folder for some guidance on setting up your dev environment and creating your first user. + +## Contributing + +We welcome contributions from all members of the community! Whether you're a seasoned developer or just getting started, there are plenty of ways to get involved. If you have ideas for new features, encounter any bugs, or simply want to help improve the platform, we'd love to hear from you. diff --git a/docs/01_dev_environment.md b/docs/01_dev_environment.md new file mode 100644 index 0000000..0832f6f --- /dev/null +++ b/docs/01_dev_environment.md @@ -0,0 +1,34 @@ +# Dev Environment + +This is a somewhat straightforward python Flask app, backed by a Postgres database. + +The repository has a dev container configured which you can use if you like. + +Requiments: +- Python 3.9+ +- PostgreSQL 14+ - installed as part of the devcontainer if you are using it. +- Some ability to run Postgres queries directly - pgAdmin is a good GUI option, while `psql` is a good CLI - both are bundled with Postgres +- Git +- Docker/podman/your container orchestration tool of choice (if using dev containers). + +Most of us use VsCode as a lightweight IDE. + +### Database Setup + +1. Connect to the database using `psql postgres` or using pgAdmin. +2. Create a database called `hackspace`. In psql, you can run the query `CREATE DATABASE hackspace;` (don't forget the semi-colon!). +3. Under the `hackspace-mgmt/migration` folder is a bunch of SQL scripts. Run these, in order, against the new hackspace database. In pgAdmin, you would right click on the database and open the `Query` tool. Then copy-paste in the contents of each file and run them one-by-one. + +If you had to change the username, then you'll want to create a postgres user. You can do this by right-clicking the server and then _Create->Login/Group role_. Name the role `postgres`, then on the _Priveleges_ tab, enable _Can Login_ and _Superuser_ (this isn't recommended for production, but fine for development). + +### Webserver Setup + +In a terminal, navigate to the `hackspace-mgmt` folder and create a virtual environment with `python3 -m venv .venv`. This environment can then be activated with `source .venv/bin/activate` or `.venv/Scripts/activate.ps1` depending on which OS/terminal you are using. + +Update pip with `python -m pip install --upgrade pip`. + +Install the requirements with `pip install -r requirements.txt`. + +You should now be able to run the server with `flask --app hackspace_mgmt:create_app --debug run`. + +Navigate to `http://127.0.0.1:5000/admin/` and you should be able to see a bare admin page! \ No newline at end of file diff --git a/docs/02_creating_your_first_user.md b/docs/02_creating_your_first_user.md new file mode 100644 index 0000000..ab7070c --- /dev/null +++ b/docs/02_creating_your_first_user.md @@ -0,0 +1,12 @@ +# Creating Your First User + +In order to use the member facing page of the server, we will need to create a test user we can login with. + +1. Create a test card. From the `/admin` page, navigate to _Membership->Card_, then click _Create_. Enter 0 as the serial for now. This is an integer, but everything else uses the hexadecimal representation. Enter 123 for the _Number On Front_, leave everything else as it is and click _Save_. +2. Create a test member. From the `/admin` page, navigate to _Membership->Member_, then click _Create_. Enter anything you want into the name and email fields. In the _Cards_ field, type the number 123 then select the entry that appears. Click _Save_ to finish. + +## Logging In + +You should now be able to navigate to `http://127.0.0.1:5000`. This will show the login page that's displayed on the member portal in the Hackspace. + +Behind the scenes, this page listens for keyboard input and will log you in when a valid card serial is "typed" in. To login with the test card, type _0 then Enter_. You will have to be quick as there is a 0.5s timeout after the page loads! \ No newline at end of file From c737d6b15eda6f153931bb4684775ff92eb78904 Mon Sep 17 00:00:00 2001 From: Damian Adil Date: Wed, 17 Apr 2024 21:17:01 +0100 Subject: [PATCH 5/7] Set port forwarding for postgres in devcontainer.json --- .devcontainer/devcontainer.json | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7642a44..b50fe9d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,24 +1,7 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/postgres { "name": "Python 3 & PostgreSQL", "dockerComposeFile": "docker-compose.yml", "service": "app", - "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}" - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // This can be used to network with other containers or the host. - // "forwardPorts": [5000, 5432], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "pip install --user -r requirements.txt", - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "forwardPorts": [ 5432 ] } From 4d45b6051c2c8a7d286dbcdba787eb674ecbdeb1 Mon Sep 17 00:00:00 2001 From: Sam <633856+SamP20@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:42:23 +0100 Subject: [PATCH 6/7] Add sample dataset --- sample_dataset.sql | 562 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 562 insertions(+) create mode 100644 sample_dataset.sql diff --git a/sample_dataset.sql b/sample_dataset.sql new file mode 100644 index 0000000..517711b --- /dev/null +++ b/sample_dataset.sql @@ -0,0 +1,562 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 16.2 +-- Dumped by pg_dump version 16.2 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: discourse_invite; Type: TYPE; Schema: public; Owner: postgres +-- + +CREATE TYPE public.discourse_invite AS ENUM ( + 'no', + 'invited', + 'emailed', + 'accepted', + 'alumni' +); + + +ALTER TYPE public.discourse_invite OWNER TO postgres; + +-- +-- Name: induction_state; Type: TYPE; Schema: public; Owner: postgres +-- + +CREATE TYPE public.induction_state AS ENUM ( + 'valid', + 'expired', + 'banned' +); + + +ALTER TYPE public.induction_state OWNER TO postgres; + +-- +-- Name: legacy_machine_auth; Type: TYPE; Schema: public; Owner: postgres +-- + +CREATE TYPE public.legacy_machine_auth AS ENUM ( + 'none', + 'password', + 'padlock' +); + + +ALTER TYPE public.legacy_machine_auth OWNER TO postgres; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: card; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.card ( + id integer NOT NULL, + card_serial bigint, + number_on_front integer, + member_id integer, + lost boolean DEFAULT false NOT NULL, + unverified_serial bigint, + door_disabled boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.card OWNER TO postgres; + +-- +-- Name: card_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.card ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.card_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: induction; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.induction ( + id integer NOT NULL, + member_id integer NOT NULL, + machine_id integer NOT NULL, + state public.induction_state NOT NULL, + inducted_by integer, + inducted_on date DEFAULT now(), + can_induct boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.induction OWNER TO postgres; + +-- +-- Name: induction_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.induction ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.induction_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: label; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.label ( + id integer NOT NULL, + member_id integer, + expiry date NOT NULL, + caption character varying(255) NOT NULL, + printed boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.label OWNER TO postgres; + +-- +-- Name: label_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.label ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.label_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: machine; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.machine ( + id integer NOT NULL, + name character varying(255) NOT NULL, + legacy_auth public.legacy_machine_auth DEFAULT 'none'::public.legacy_machine_auth NOT NULL, + legacy_password character varying(255) DEFAULT ''::character varying NOT NULL, + hide_from_home boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.machine OWNER TO postgres; + +-- +-- Name: machine_controller; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.machine_controller ( + id integer NOT NULL, + machine_id integer, + requires_update boolean NOT NULL, + powered boolean DEFAULT false NOT NULL, + idle_timeout integer DEFAULT '-1'::integer NOT NULL, + idle_power_threshold integer DEFAULT 50 NOT NULL, + invert_logout_button boolean DEFAULT false NOT NULL, + hostname character varying(255) DEFAULT ''::character varying NOT NULL +); + + +ALTER TABLE public.machine_controller OWNER TO postgres; + +-- +-- Name: machine_controller_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.machine_controller ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.machine_controller_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: machine_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.machine ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.machine_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: member; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.member ( + id integer NOT NULL, + first_name character varying(80) NOT NULL, + last_name character varying(80), + discourse_id integer, + discourse public.discourse_invite DEFAULT 'no'::public.discourse_invite NOT NULL, + newsletter boolean DEFAULT false NOT NULL, + email character varying(300), + alt_email character varying(300), + payment_ref character varying(200), + join_date date DEFAULT CURRENT_DATE NOT NULL, + end_date date, + end_reason character varying(500), + address1 character varying(200) DEFAULT ''::character varying NOT NULL, + address2 character varying(200) DEFAULT ''::character varying NOT NULL, + town_city character varying(200) DEFAULT ''::character varying NOT NULL, + county character varying(200) DEFAULT ''::character varying NOT NULL, + postcode character varying(20) DEFAULT ''::character varying NOT NULL, + payment_active boolean DEFAULT false NOT NULL, + notes text, + preferred_name character varying(160), + welcome_email_sent boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.member OWNER TO postgres; + +-- +-- Name: member_data_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.member ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.member_data_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: quiz; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.quiz ( + id integer NOT NULL, + title character varying(200) NOT NULL, + description text, + questions text NOT NULL, + machine_id integer, + intro text DEFAULT ''::text NOT NULL, + hidden boolean DEFAULT false NOT NULL +); + + +ALTER TABLE public.quiz OWNER TO postgres; + +-- +-- Name: quiz_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.quiz ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.quiz_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Data for Name: card; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.card (id, card_serial, number_on_front, member_id, lost, unverified_serial, door_disabled) FROM stdin; +1 2864434397 111 \N f \N f +2 1122867 112 \N f \N f +3 2882351791 113 \N f \N f +4 0 100 7 f \N f +\. + + +-- +-- Data for Name: induction; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.induction (id, member_id, machine_id, state, inducted_by, inducted_on, can_induct) FROM stdin; +1 7 2 valid \N 2024-04-25 f +\. + + +-- +-- Data for Name: label; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.label (id, member_id, expiry, caption, printed) FROM stdin; +\. + + +-- +-- Data for Name: machine; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.machine (id, name, legacy_auth, legacy_password, hide_from_home) FROM stdin; +1 Machine 1 none f +2 Machine 2 padlock 1234 f +3 Hidden Machine none t +\. + + +-- +-- Data for Name: machine_controller; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.machine_controller (id, machine_id, requires_update, powered, idle_timeout, idle_power_threshold, invert_logout_button, hostname) FROM stdin; +1 1 f f -1 50 f aabbccdd +2 2 f f -1 50 f eeff0011 +\. + + +-- +-- Data for Name: member; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.member (id, first_name, last_name, discourse_id, discourse, newsletter, email, alt_email, payment_ref, join_date, end_date, end_reason, address1, address2, town_city, county, postcode, payment_active, notes, preferred_name, welcome_email_sent) FROM stdin; +1 Elliot Fisher \N no f elliotf@example.com \N \N 2024-04-25 \N \N f \N \N f +2 Donte Huang \N no f donte-huang@example.com \N \N 2024-04-25 \N \N f \N \N f +3 Leo Gates \N no f leog@example.com \N \N 2024-04-25 \N \N f \N \N f +4 Annika Arellano \N no f aarellano@example.com \N \N 2024-04-25 \N \N f \N \N f +5 Samson Roth \N no f sroth@example.com \N \N 2024-04-25 \N \N f \N \N f +6 Quinn Nobel \N no f quinnn@example.com \N \N 2024-04-25 \N \N f \N \N f +7 Example User \N no f example@example.com \N \N 2024-04-25 \N \N f \N \N f +\. + + +-- +-- Data for Name: quiz; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.quiz (id, title, description, questions, machine_id, intro, hidden) FROM stdin; +1 Machine 1 Quiz Quiz for the first machine q1:\r\n type: pick_one\r\n correct_answer: a1\r\n label: Select the correct answer\r\n answers:\r\n a1: This is correct\r\n a2: This is wrong\r\n a3: This is also wrong\r\nq2:\r\n type: select_all\r\n correct_answers: ["a1", "a2"]\r\n label: Select the correct answers\r\n answers:\r\n a1: This is correct\r\n a2: This is also correct\r\n a3: This is wrong\r\nq3:\r\n type: yes_no\r\n correct_answer: yes\r\n label: You must tick this answer. 1 Welcome to the example quiz. Here's some different question types. f +\. + + +-- +-- Name: card_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.card_id_seq', 4, true); + + +-- +-- Name: induction_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.induction_id_seq', 1, true); + + +-- +-- Name: label_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.label_id_seq', 1, false); + + +-- +-- Name: machine_controller_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.machine_controller_id_seq', 2, true); + + +-- +-- Name: machine_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.machine_id_seq', 3, true); + + +-- +-- Name: member_data_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.member_data_id_seq', 7, true); + + +-- +-- Name: quiz_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.quiz_id_seq', 1, true); + + +-- +-- Name: card card_card_serial_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.card + ADD CONSTRAINT card_card_serial_key UNIQUE (card_serial); + + +-- +-- Name: card card_number_on_front_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.card + ADD CONSTRAINT card_number_on_front_key UNIQUE (number_on_front); + + +-- +-- Name: card card_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.card + ADD CONSTRAINT card_pkey PRIMARY KEY (id); + + +-- +-- Name: induction induction_member_id_machine_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.induction + ADD CONSTRAINT induction_member_id_machine_id_key UNIQUE (member_id, machine_id); + + +-- +-- Name: induction induction_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.induction + ADD CONSTRAINT induction_pkey PRIMARY KEY (id); + + +-- +-- Name: label label_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.label + ADD CONSTRAINT label_pkey PRIMARY KEY (id); + + +-- +-- Name: machine_controller machine_controller_hostname_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.machine_controller + ADD CONSTRAINT machine_controller_hostname_key UNIQUE (hostname); + + +-- +-- Name: machine_controller machine_controller_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.machine_controller + ADD CONSTRAINT machine_controller_pkey PRIMARY KEY (id); + + +-- +-- Name: machine machine_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.machine + ADD CONSTRAINT machine_pkey PRIMARY KEY (id); + + +-- +-- Name: member member_data_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.member + ADD CONSTRAINT member_data_pkey PRIMARY KEY (id); + + +-- +-- Name: quiz quiz_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.quiz + ADD CONSTRAINT quiz_pkey PRIMARY KEY (id); + + +-- +-- Name: card card_member_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.card + ADD CONSTRAINT card_member_id_fkey FOREIGN KEY (member_id) REFERENCES public.member(id) NOT VALID; + + +-- +-- Name: induction induction_inducted_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.induction + ADD CONSTRAINT induction_inducted_by_fkey FOREIGN KEY (inducted_by) REFERENCES public.member(id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID; + + +-- +-- Name: induction induction_machine_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.induction + ADD CONSTRAINT induction_machine_id_fkey FOREIGN KEY (machine_id) REFERENCES public.machine(id) ON UPDATE RESTRICT ON DELETE RESTRICT; + + +-- +-- Name: induction induction_member_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.induction + ADD CONSTRAINT induction_member_id_fkey FOREIGN KEY (member_id) REFERENCES public.member(id) ON UPDATE RESTRICT ON DELETE RESTRICT NOT VALID; + + +-- +-- Name: label label_member_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.label + ADD CONSTRAINT label_member_id_fkey FOREIGN KEY (member_id) REFERENCES public.member(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: machine_controller machine_controller_machine_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.machine_controller + ADD CONSTRAINT machine_controller_machine_id_fkey FOREIGN KEY (machine_id) REFERENCES public.machine(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- Name: quiz quiz_machine_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.quiz + ADD CONSTRAINT quiz_machine_id_fkey FOREIGN KEY (machine_id) REFERENCES public.machine(id) ON UPDATE CASCADE ON DELETE SET NULL; + + +-- +-- PostgreSQL database dump complete +-- + From a0bb8fa7a409102a9b17bb897e996d146be13738 Mon Sep 17 00:00:00 2001 From: Damian Adil Date: Sun, 5 May 2024 15:15:20 +0000 Subject: [PATCH 7/7] Move towards Flask-Migrate for database schema management, pin dev container app and db images to ensure compatible versions of postgres tooling, re-dump sample_dataset to contain schema version OOTB --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 2 +- .devcontainer/docker-compose.yml | 2 +- .vscode/launch.json | 1 - .vscode/tasks.json | 10 - docs/01_dev_environment.md | 11 +- hackspace_mgmt/__init__.py | 4 +- hackspace_mgmt/models.py | 3 +- migrate.sh | 26 --- migration/00_initial.sql | 327 --------------------------- migration/01_preferred_name.sql | 5 - migration/02_preferred_name.sql | 7 - migration/03_newsletter.sql | 2 - migration/04_machine_power.sql | 2 - migration/05_label.sql | 14 -- migration/06_controller_settings.sql | 8 - migration/07_discourse_emailed.sql | 1 - migration/08_welcome_email.sql | 5 - migration/09_card_verified.sql | 2 - migration/10_quiz.sql | 14 -- migration/11_unique_induction.sql | 2 - migration/12_alumni.sql | 2 - migration/13_card_disabled.sql | 2 - migration/14_quiz_intro.sql | 5 - migration/15_machine_hostname.sql | 6 - migration/16_machine_legacy.sql | 11 - migration/17_hidden_machine.sql | 1 - migration/18_can_induct.sql | 2 - migration/19_address_not_null.sql | 39 ---- migrations/README | 1 + migrations/alembic.ini | 50 ++++ migrations/env.py | 113 +++++++++ migrations/script.py.mako | 24 ++ migrations/versions/0f17df0ee1a8_.py | 242 ++++++++++++++++++++ requirements.txt | 1 + sample_dataset.sql | 32 ++- 36 files changed, 474 insertions(+), 507 deletions(-) delete mode 100644 .vscode/tasks.json delete mode 100644 migrate.sh delete mode 100644 migration/00_initial.sql delete mode 100644 migration/01_preferred_name.sql delete mode 100644 migration/02_preferred_name.sql delete mode 100644 migration/03_newsletter.sql delete mode 100644 migration/04_machine_power.sql delete mode 100644 migration/05_label.sql delete mode 100644 migration/06_controller_settings.sql delete mode 100644 migration/07_discourse_emailed.sql delete mode 100644 migration/08_welcome_email.sql delete mode 100644 migration/09_card_verified.sql delete mode 100644 migration/10_quiz.sql delete mode 100644 migration/11_unique_induction.sql delete mode 100644 migration/12_alumni.sql delete mode 100644 migration/13_card_disabled.sql delete mode 100644 migration/14_quiz_intro.sql delete mode 100644 migration/15_machine_hostname.sql delete mode 100644 migration/16_machine_legacy.sql delete mode 100644 migration/17_hidden_machine.sql delete mode 100644 migration/18_can_induct.sql delete mode 100644 migration/19_address_not_null.sql create mode 100644 migrations/README create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/0f17df0ee1a8_.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ccab5a5..616e6dd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/python:0-3.11 +FROM mcr.microsoft.com/devcontainers/python:3.12 ENV PYTHONUNBUFFERED 1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8525edc..7b73c7c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,6 +9,6 @@ "extensions": [ "ms-python.python" ] - } + } } } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 019c068..b0d2c1c 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -11,7 +11,7 @@ services: network_mode: service:db db: - image: postgres:latest + image: postgres:15.6 restart: unless-stopped volumes: - postgres-data:/var/lib/postgresql/data diff --git a/.vscode/launch.json b/.vscode/launch.json index e1bf8a6..ca8de79 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,6 @@ "type": "debugpy", "request": "launch", "module": "flask", - "preLaunchTask": "Migrate", "env": { "FLASK_APP": "hackspace_mgmt/__init__.py", "FLASK_DEBUG": "1" diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 82bc16f..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Migrate", - "type": "shell", - "command": ". ${workspaceFolder}/migrate.sh" - } - ] -} \ No newline at end of file diff --git a/docs/01_dev_environment.md b/docs/01_dev_environment.md index 0f448c4..d386305 100644 --- a/docs/01_dev_environment.md +++ b/docs/01_dev_environment.md @@ -11,7 +11,7 @@ The repository has a dev container configured which you can use if you wish. Thi 3. Clone and open the repository. Vscode will nudge you to open it in a dev container. 4. Click "Run" in the debug pane. -Note: Both the dev container and postgres can be deleted and rebuilt as needed without losing your DB. They will automatically re-attach themselves to the `hackspace-mgmt_devcontainer_postgres-data` volume which is the important one not to lose as it contains the database itself. +Both the dev container and postgres can be deleted and rebuilt as needed without losing your DB. On recreate, they will automatically re-attach themselves to the `hackspace-mgmt_devcontainer_postgres-data` volume. This contains the data itself, so it's the important one to keep safe. ### Without the Dev Container @@ -38,10 +38,9 @@ Navigate to `http://127.0.0.1:5000/admin/` and you should be able to see a bare ### Database Setup -The database is nominally configured by running each of the SQL scripts inside the `migration` folder sequentially. Anytime a new one is added, this will need to be applied before the app is started. +The database schema is managed by the `Flask-Migrate` package, which will automatically create the database and update the schema for you on app startup. -If you're using a dev container, the `migrate.sh` script will be run prior to any debug session which does this automagically! +However, `./sample_dataset.sql` contains a pg_dump which can be useful for development environments as it contains a realistic set of data to test with. -Tooling-wise, you may wish to use `psql` - a CLI for interacting with the datbase directly. The dev container comes with it installed, but because it is running in a container, you need to use `psql -h localhost -U postgres (-d hackspace) etc` to connect. - -Separately, you may find it helpful to install `pgAdmin`. This is a free GUI tool for exploring Postgres, similar to SSMS. +1. Create a fresh database using `psql -h localhost -U postgres -c "CREATE DATABASE hackspace"`. +2. Apply the dump using `psql -h localhost -U postgres hackspace < sample_dataset.sql`. diff --git a/hackspace_mgmt/__init__.py b/hackspace_mgmt/__init__.py index e068fc8..4e59378 100644 --- a/hackspace_mgmt/__init__.py +++ b/hackspace_mgmt/__init__.py @@ -28,8 +28,10 @@ def create_app(test_config=None): scss = Bundle('scss/main.scss', filters='pyscss', depends=('scss/**/*.scss'), output='css/all.css') assets.register('css_all', scss) - from .models import db + from .models import db, migrate + db.init_app(app) + migrate.init_app(app, db) from . import general general.init_app(app) diff --git a/hackspace_mgmt/models.py b/hackspace_mgmt/models.py index ac8ee78..19ceeb9 100644 --- a/hackspace_mgmt/models.py +++ b/hackspace_mgmt/models.py @@ -8,9 +8,10 @@ from datetime import date from flask_sqlalchemy import SQLAlchemy +from flask_migrate import Migrate db = SQLAlchemy() - +migrate = Migrate() class InductionState(enum.Enum): valid = "valid" diff --git a/migrate.sh b/migrate.sh deleted file mode 100644 index 3625c5b..0000000 --- a/migrate.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# Create the hackspace DB if it doesn't already exist -psql -h localhost -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = 'hackspace'" | \ - grep -q 1 || psql -h localhost -U postgres -c "CREATE DATABASE hackspace" - -# Create the schema_version file if it doesn't exist -if ! [ -e last_migrated.txt ] ; then - echo -n "-1" > last_migrated.txt -fi - -last_migrated_file=$( cat last_migrated.txt ) -needs_migrating=false - -for file in migration/* -do - if [ "$last_migrated_file" = file ] || [ "$last_migrated_file" = "-1" ] ; then - needs_migrating=true - fi - - if [ "$needs_migrating" = true ] ; then - echo "Migrating $file" - psql -h localhost -U postgres -d hackspace -f "$file" - echo "$file" > last_migrated.txt - fi -done diff --git a/migration/00_initial.sql b/migration/00_initial.sql deleted file mode 100644 index c3bc313..0000000 --- a/migration/00_initial.sql +++ /dev/null @@ -1,327 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 13.10 (Debian 13.10-0+deb11u1) --- Dumped by pg_dump version 14.3 - --- Started on 2023-07-23 14:54:27 BST - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - - --- --- TOC entry 653 (class 1247 OID 16465) --- Name: discourse_invite; Type: TYPE; Schema: public; Owner: - --- - -CREATE TYPE public.discourse_invite AS ENUM ( - 'no', - 'invited', - 'expired', - 'accepted' -); - - --- --- TOC entry 632 (class 1247 OID 16388) --- Name: induction_state; Type: TYPE; Schema: public; Owner: - --- - -CREATE TYPE public.induction_state AS ENUM ( - 'valid', - 'expired', - 'banned' -); - - -SET default_table_access_method = heap; - --- --- TOC entry 203 (class 1259 OID 16414) --- Name: card; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.card ( - id integer NOT NULL, - card_serial bigint, - number_on_front integer, - member_id integer, - lost boolean DEFAULT false NOT NULL -); - - --- --- TOC entry 202 (class 1259 OID 16412) --- Name: card_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -ALTER TABLE public.card ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.card_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1 -); - - --- --- TOC entry 209 (class 1259 OID 16451) --- Name: induction; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.induction ( - id integer NOT NULL, - member_id integer NOT NULL, - machine_id integer NOT NULL, - state public.induction_state NOT NULL, - inducted_by integer, - inducted_on date DEFAULT now() -); - - --- --- TOC entry 208 (class 1259 OID 16449) --- Name: induction_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -ALTER TABLE public.induction ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.induction_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1 -); - - --- --- TOC entry 205 (class 1259 OID 16425) --- Name: machine; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.machine ( - id integer NOT NULL, - name character varying(255) NOT NULL -); - - --- --- TOC entry 207 (class 1259 OID 16437) --- Name: machine_controller; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.machine_controller ( - id integer NOT NULL, - mac bigint, - machine_id integer, - requires_update boolean NOT NULL -); - - --- --- TOC entry 206 (class 1259 OID 16435) --- Name: machine_controller_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -ALTER TABLE public.machine_controller ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.machine_controller_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1 -); - - --- --- TOC entry 204 (class 1259 OID 16423) --- Name: machine_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -ALTER TABLE public.machine ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.machine_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1 -); - - --- --- TOC entry 200 (class 1259 OID 16395) --- Name: member; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.member ( - id integer NOT NULL, - first_name character varying(80) NOT NULL, - last_name character varying(80), - discourse_id integer, - discourse public.discourse_invite DEFAULT 'no'::public.discourse_invite NOT NULL, - mailchimp boolean DEFAULT false NOT NULL, - email character varying(300), - alt_email character varying(300), - payment_ref character varying(200), - join_date date DEFAULT CURRENT_DATE NOT NULL, - end_date date, - end_reason character varying(500), - address1 character varying(200), - address2 character varying(200), - town_city character varying(200), - county character varying(200), - postcode character varying(20), - payment_active boolean DEFAULT false NOT NULL, - notes text -); - - --- --- TOC entry 201 (class 1259 OID 16406) --- Name: member_data_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -ALTER TABLE public.member ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( - SEQUENCE NAME public.member_data_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1 -); - - --- --- TOC entry 2893 (class 2606 OID 16420) --- Name: card card_card_serial_key; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.card - ADD CONSTRAINT card_card_serial_key UNIQUE (card_serial); - - --- --- TOC entry 2895 (class 2606 OID 16422) --- Name: card card_number_on_front_key; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.card - ADD CONSTRAINT card_number_on_front_key UNIQUE (number_on_front); - - --- --- TOC entry 2897 (class 2606 OID 16418) --- Name: card card_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.card - ADD CONSTRAINT card_pkey PRIMARY KEY (id); - - --- --- TOC entry 2905 (class 2606 OID 16455) --- Name: induction induction_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.induction - ADD CONSTRAINT induction_pkey PRIMARY KEY (id); - - --- --- TOC entry 2901 (class 2606 OID 16443) --- Name: machine_controller machine_controller_mac_key; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.machine_controller - ADD CONSTRAINT machine_controller_mac_key UNIQUE (mac); - - --- --- TOC entry 2903 (class 2606 OID 16441) --- Name: machine_controller machine_controller_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.machine_controller - ADD CONSTRAINT machine_controller_pkey PRIMARY KEY (id); - - --- --- TOC entry 2899 (class 2606 OID 16429) --- Name: machine machine_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.machine - ADD CONSTRAINT machine_pkey PRIMARY KEY (id); - - --- --- TOC entry 2891 (class 2606 OID 16399) --- Name: member member_data_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.member - ADD CONSTRAINT member_data_pkey PRIMARY KEY (id); - - --- --- TOC entry 2906 (class 2606 OID 24654) --- Name: card card_member_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.card - ADD CONSTRAINT card_member_id_fkey FOREIGN KEY (member_id) REFERENCES public.member(id) NOT VALID; - - --- --- TOC entry 2910 (class 2606 OID 24664) --- Name: induction induction_inducted_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.induction - ADD CONSTRAINT induction_inducted_by_fkey FOREIGN KEY (inducted_by) REFERENCES public.member(id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID; - - --- --- TOC entry 2908 (class 2606 OID 16456) --- Name: induction induction_machine_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.induction - ADD CONSTRAINT induction_machine_id_fkey FOREIGN KEY (machine_id) REFERENCES public.machine(id) ON UPDATE RESTRICT ON DELETE RESTRICT; - - --- --- TOC entry 2909 (class 2606 OID 24659) --- Name: induction induction_member_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.induction - ADD CONSTRAINT induction_member_id_fkey FOREIGN KEY (member_id) REFERENCES public.member(id) ON UPDATE RESTRICT ON DELETE RESTRICT NOT VALID; - - --- --- TOC entry 2907 (class 2606 OID 16444) --- Name: machine_controller machine_controller_machine_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.machine_controller - ADD CONSTRAINT machine_controller_machine_id_fkey FOREIGN KEY (machine_id) REFERENCES public.machine(id) ON UPDATE CASCADE ON DELETE SET NULL; - - --- Completed on 2023-07-23 14:54:28 BST - --- --- PostgreSQL database dump complete --- - diff --git a/migration/01_preferred_name.sql b/migration/01_preferred_name.sql deleted file mode 100644 index ebffaa3..0000000 --- a/migration/01_preferred_name.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE IF EXISTS public.member - ADD COLUMN preferred_first_name character varying(80); - -ALTER TABLE IF EXISTS public.member - ADD COLUMN preferred_last_name character varying(80); \ No newline at end of file diff --git a/migration/02_preferred_name.sql b/migration/02_preferred_name.sql deleted file mode 100644 index e93b976..0000000 --- a/migration/02_preferred_name.sql +++ /dev/null @@ -1,7 +0,0 @@ -ALTER TABLE IF EXISTS public.member DROP COLUMN IF EXISTS preferred_last_name; - -ALTER TABLE IF EXISTS public.member - RENAME preferred_first_name TO preferred_name; - -ALTER TABLE public.member - ALTER COLUMN preferred_name TYPE character varying(160) COLLATE pg_catalog."default"; \ No newline at end of file diff --git a/migration/03_newsletter.sql b/migration/03_newsletter.sql deleted file mode 100644 index 405410b..0000000 --- a/migration/03_newsletter.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE IF EXISTS public.member - RENAME mailchimp TO newsletter; \ No newline at end of file diff --git a/migration/04_machine_power.sql b/migration/04_machine_power.sql deleted file mode 100644 index ee9331c..0000000 --- a/migration/04_machine_power.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE IF EXISTS public.machine_controller - ADD COLUMN powered boolean NOT NULL DEFAULT false; \ No newline at end of file diff --git a/migration/05_label.sql b/migration/05_label.sql deleted file mode 100644 index 558eacc..0000000 --- a/migration/05_label.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE public.label -( - id integer NOT NULL GENERATED ALWAYS AS IDENTITY, - member_id integer, - expiry date NOT NULL, - caption character varying(255) NOT NULL, - printed boolean NOT NULL DEFAULT false, - PRIMARY KEY (id), - FOREIGN KEY (member_id) - REFERENCES public.member (id) MATCH SIMPLE - ON UPDATE CASCADE - ON DELETE SET NULL - NOT VALID -); \ No newline at end of file diff --git a/migration/06_controller_settings.sql b/migration/06_controller_settings.sql deleted file mode 100644 index 7032ec6..0000000 --- a/migration/06_controller_settings.sql +++ /dev/null @@ -1,8 +0,0 @@ -ALTER TABLE IF EXISTS public.machine_controller - ADD COLUMN idle_timeout integer NOT NULL DEFAULT -1; - -ALTER TABLE IF EXISTS public.machine_controller - ADD COLUMN idle_power_threshold integer NOT NULL DEFAULT 50; - -ALTER TABLE IF EXISTS public.machine_controller - ADD COLUMN invert_logout_button boolean NOT NULL DEFAULT false; \ No newline at end of file diff --git a/migration/07_discourse_emailed.sql b/migration/07_discourse_emailed.sql deleted file mode 100644 index defaa65..0000000 --- a/migration/07_discourse_emailed.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TYPE public.discourse_invite RENAME VALUE 'expired' TO 'emailed' \ No newline at end of file diff --git a/migration/08_welcome_email.sql b/migration/08_welcome_email.sql deleted file mode 100644 index 65d75a7..0000000 --- a/migration/08_welcome_email.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE IF EXISTS public.member - ADD COLUMN welcome_email_sent boolean NOT NULL DEFAULT true; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN welcome_email_sent SET DEFAULT false; \ No newline at end of file diff --git a/migration/09_card_verified.sql b/migration/09_card_verified.sql deleted file mode 100644 index 8482499..0000000 --- a/migration/09_card_verified.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE IF EXISTS public.card - ADD COLUMN unverified_serial bigint; \ No newline at end of file diff --git a/migration/10_quiz.sql b/migration/10_quiz.sql deleted file mode 100644 index 97facff..0000000 --- a/migration/10_quiz.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE public.quiz -( - id integer NOT NULL GENERATED ALWAYS AS IDENTITY, - title character varying(200) NOT NULL, - description text, - questions text NOT NULL, - machine_id integer, - PRIMARY KEY (id), - FOREIGN KEY (machine_id) - REFERENCES public.machine (id) MATCH SIMPLE - ON UPDATE CASCADE - ON DELETE SET NULL - NOT VALID -); \ No newline at end of file diff --git a/migration/11_unique_induction.sql b/migration/11_unique_induction.sql deleted file mode 100644 index d0211c8..0000000 --- a/migration/11_unique_induction.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE IF EXISTS public.induction - ADD UNIQUE (member_id, machine_id); \ No newline at end of file diff --git a/migration/12_alumni.sql b/migration/12_alumni.sql deleted file mode 100644 index 8152667..0000000 --- a/migration/12_alumni.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TYPE public.discourse_invite - ADD VALUE 'alumni' AFTER 'accepted'; \ No newline at end of file diff --git a/migration/13_card_disabled.sql b/migration/13_card_disabled.sql deleted file mode 100644 index bc238f3..0000000 --- a/migration/13_card_disabled.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE IF EXISTS public.card - ADD COLUMN door_disabled boolean NOT NULL DEFAULT false; \ No newline at end of file diff --git a/migration/14_quiz_intro.sql b/migration/14_quiz_intro.sql deleted file mode 100644 index b6154dc..0000000 --- a/migration/14_quiz_intro.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE IF EXISTS public.quiz - ADD COLUMN intro text NOT NULL DEFAULT ''; - -ALTER TABLE IF EXISTS public.quiz - ADD COLUMN hidden boolean NOT NULL DEFAULT false; \ No newline at end of file diff --git a/migration/15_machine_hostname.sql b/migration/15_machine_hostname.sql deleted file mode 100644 index 22fc89d..0000000 --- a/migration/15_machine_hostname.sql +++ /dev/null @@ -1,6 +0,0 @@ -ALTER TABLE IF EXISTS public.machine_controller - ADD COLUMN hostname character varying(255) NOT NULL DEFAULT ''; -UPDATE public.machine_controller SET hostname = mac WHERE hostname = ''; -ALTER TABLE IF EXISTS public.machine_controller - ADD UNIQUE (hostname); -ALTER TABLE IF EXISTS public.machine_controller DROP COLUMN IF EXISTS mac; \ No newline at end of file diff --git a/migration/16_machine_legacy.sql b/migration/16_machine_legacy.sql deleted file mode 100644 index aed46a4..0000000 --- a/migration/16_machine_legacy.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TYPE public.legacy_machine_auth AS ENUM ( - 'none', - 'password', - 'padlock' -); - -ALTER TABLE IF EXISTS public.machine - ADD COLUMN legacy_auth public.legacy_machine_auth NOT NULL DEFAULT 'none'::public.legacy_machine_auth; - -ALTER TABLE IF EXISTS public.machine - ADD COLUMN legacy_password character varying(255) NOT NULL DEFAULT ''; \ No newline at end of file diff --git a/migration/17_hidden_machine.sql b/migration/17_hidden_machine.sql deleted file mode 100644 index f830176..0000000 --- a/migration/17_hidden_machine.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE public.machine ADD COLUMN hide_from_home boolean NOT NULL DEFAULT false; \ No newline at end of file diff --git a/migration/18_can_induct.sql b/migration/18_can_induct.sql deleted file mode 100644 index b103e14..0000000 --- a/migration/18_can_induct.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE IF EXISTS public.induction - ADD COLUMN can_induct boolean NOT NULL DEFAULT false; \ No newline at end of file diff --git a/migration/19_address_not_null.sql b/migration/19_address_not_null.sql deleted file mode 100644 index 2cdc656..0000000 --- a/migration/19_address_not_null.sql +++ /dev/null @@ -1,39 +0,0 @@ -UPDATE public.member SET address1 = '' where address1 IS NULL; - -UPDATE public.member SET address2 = '' where address2 IS NULL; - -UPDATE public.member SET town_city = '' where town_city IS NULL; - -UPDATE public.member SET county = '' where county IS NULL; - -UPDATE public.member SET postcode = '' where postcode IS NULL; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN address1 SET DEFAULT ''; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN address1 SET NOT NULL; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN address2 SET DEFAULT ''; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN address2 SET NOT NULL; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN town_city SET DEFAULT ''; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN town_city SET NOT NULL; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN county SET DEFAULT ''; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN county SET NOT NULL; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN postcode SET DEFAULT ''; - -ALTER TABLE IF EXISTS public.member - ALTER COLUMN postcode SET NOT NULL; \ No newline at end of file diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000..0e04844 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..4c97092 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/0f17df0ee1a8_.py b/migrations/versions/0f17df0ee1a8_.py new file mode 100644 index 0000000..e1e1526 --- /dev/null +++ b/migrations/versions/0f17df0ee1a8_.py @@ -0,0 +1,242 @@ +"""empty message + +Revision ID: 0f17df0ee1a8 +Revises: +Create Date: 2024-05-01 20:28:38.516679 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '0f17df0ee1a8' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('card', schema=None) as batch_op: + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=None, + existing_nullable=False, + autoincrement=True) + batch_op.alter_column('card_serial', + existing_type=sa.BIGINT(), + type_=sa.Integer(), + existing_nullable=True) + batch_op.alter_column('unverified_serial', + existing_type=sa.BIGINT(), + type_=sa.Integer(), + existing_nullable=True) + batch_op.create_unique_constraint(None, ['unverified_serial']) + + with op.batch_alter_table('induction', schema=None) as batch_op: + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=None, + existing_nullable=False, + autoincrement=True) + batch_op.alter_column('inducted_on', + existing_type=sa.DATE(), + nullable=False, + existing_server_default=sa.text('now()')) + batch_op.drop_constraint('induction_machine_id_fkey', type_='foreignkey') + batch_op.drop_constraint('induction_inducted_by_fkey', type_='foreignkey') + batch_op.drop_constraint('induction_member_id_fkey', type_='foreignkey') + batch_op.create_foreign_key(None, 'member', ['member_id'], ['id']) + batch_op.create_foreign_key(None, 'member', ['inducted_by'], ['id']) + batch_op.create_foreign_key(None, 'machine', ['machine_id'], ['id']) + + with op.batch_alter_table('label', schema=None) as batch_op: + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=None, + existing_nullable=False, + autoincrement=True) + batch_op.drop_constraint('label_member_id_fkey', type_='foreignkey') + batch_op.create_foreign_key(None, 'member', ['member_id'], ['id']) + + with op.batch_alter_table('machine', schema=None) as batch_op: + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=None, + existing_nullable=False, + autoincrement=True) + batch_op.alter_column('name', + existing_type=sa.VARCHAR(length=255), + type_=sa.String(length=100), + existing_nullable=False) + batch_op.alter_column('legacy_auth', + existing_type=postgresql.ENUM('none', 'password', 'padlock', name='legacy_machine_auth'), + type_=sa.Enum('none', 'password', 'padlock', name='legacy_auth'), + existing_nullable=False, + existing_server_default=sa.text("'none'::legacy_machine_auth")) + + with op.batch_alter_table('machine_controller', schema=None) as batch_op: + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=None, + existing_nullable=False, + autoincrement=True) + batch_op.alter_column('machine_id', + existing_type=sa.INTEGER(), + nullable=False) + batch_op.drop_constraint('machine_controller_machine_id_fkey', type_='foreignkey') + batch_op.create_foreign_key(None, 'machine', ['machine_id'], ['id']) + + with op.batch_alter_table('member', schema=None) as batch_op: + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=None, + existing_nullable=False, + autoincrement=True) + batch_op.alter_column('notes', + existing_type=sa.TEXT(), + type_=sa.String(), + existing_nullable=True) + batch_op.create_unique_constraint(None, ['discourse_id']) + + with op.batch_alter_table('quiz', schema=None) as batch_op: + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=None, + existing_nullable=False, + autoincrement=True) + batch_op.alter_column('title', + existing_type=sa.VARCHAR(length=200), + type_=sa.String(length=255), + existing_nullable=False) + batch_op.alter_column('description', + existing_type=sa.TEXT(), + type_=sa.String(), + existing_nullable=True) + batch_op.alter_column('questions', + existing_type=sa.TEXT(), + type_=sa.String(), + existing_nullable=False) + batch_op.alter_column('intro', + existing_type=sa.TEXT(), + type_=sa.String(), + existing_nullable=False, + existing_server_default=sa.text("''::text")) + batch_op.drop_constraint('quiz_machine_id_fkey', type_='foreignkey') + batch_op.create_foreign_key(None, 'machine', ['machine_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('quiz', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('quiz_machine_id_fkey', 'machine', ['machine_id'], ['id'], onupdate='CASCADE', ondelete='SET NULL') + batch_op.alter_column('intro', + existing_type=sa.String(), + type_=sa.TEXT(), + existing_nullable=False, + existing_server_default=sa.text("''::text")) + batch_op.alter_column('questions', + existing_type=sa.String(), + type_=sa.TEXT(), + existing_nullable=False) + batch_op.alter_column('description', + existing_type=sa.String(), + type_=sa.TEXT(), + existing_nullable=True) + batch_op.alter_column('title', + existing_type=sa.String(length=255), + type_=sa.VARCHAR(length=200), + existing_nullable=False) + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=2147483647, cycle=False, cache=1), + existing_nullable=False, + autoincrement=True) + + with op.batch_alter_table('member', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='unique') + batch_op.alter_column('notes', + existing_type=sa.String(), + type_=sa.TEXT(), + existing_nullable=True) + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=2147483647, cycle=False, cache=1), + existing_nullable=False, + autoincrement=True) + + with op.batch_alter_table('machine_controller', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('machine_controller_machine_id_fkey', 'machine', ['machine_id'], ['id'], onupdate='CASCADE', ondelete='SET NULL') + batch_op.alter_column('machine_id', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=2147483647, cycle=False, cache=1), + existing_nullable=False, + autoincrement=True) + + with op.batch_alter_table('machine', schema=None) as batch_op: + batch_op.alter_column('legacy_auth', + existing_type=sa.Enum('none', 'password', 'padlock', name='legacy_auth'), + type_=postgresql.ENUM('none', 'password', 'padlock', name='legacy_machine_auth'), + existing_nullable=False, + existing_server_default=sa.text("'none'::legacy_machine_auth")) + batch_op.alter_column('name', + existing_type=sa.String(length=100), + type_=sa.VARCHAR(length=255), + existing_nullable=False) + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=2147483647, cycle=False, cache=1), + existing_nullable=False, + autoincrement=True) + + with op.batch_alter_table('label', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('label_member_id_fkey', 'member', ['member_id'], ['id'], onupdate='CASCADE', ondelete='SET NULL') + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=2147483647, cycle=False, cache=1), + existing_nullable=False, + autoincrement=True) + + with op.batch_alter_table('induction', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.drop_constraint(None, type_='foreignkey') + batch_op.create_foreign_key('induction_member_id_fkey', 'member', ['member_id'], ['id'], onupdate='RESTRICT', ondelete='RESTRICT') + batch_op.create_foreign_key('induction_inducted_by_fkey', 'member', ['inducted_by'], ['id'], onupdate='CASCADE', ondelete='SET NULL') + batch_op.create_foreign_key('induction_machine_id_fkey', 'machine', ['machine_id'], ['id'], onupdate='RESTRICT', ondelete='RESTRICT') + batch_op.alter_column('inducted_on', + existing_type=sa.DATE(), + nullable=True, + existing_server_default=sa.text('now()')) + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=2147483647, cycle=False, cache=1), + existing_nullable=False, + autoincrement=True) + + with op.batch_alter_table('card', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='unique') + batch_op.alter_column('unverified_serial', + existing_type=sa.Integer(), + type_=sa.BIGINT(), + existing_nullable=True) + batch_op.alter_column('card_serial', + existing_type=sa.Integer(), + type_=sa.BIGINT(), + existing_nullable=True) + batch_op.alter_column('id', + existing_type=sa.INTEGER(), + server_default=sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=2147483647, cycle=False, cache=1), + existing_nullable=False, + autoincrement=True) + + # ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index 2232d97..cbb5518 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ flask>=2.0 yarl requests +Flask-Migrate Flask-SQLAlchemy psycopg2-binary>=2.9.0 Flask-Admin diff --git a/sample_dataset.sql b/sample_dataset.sql index 517711b..f579f90 100644 --- a/sample_dataset.sql +++ b/sample_dataset.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 16.2 --- Dumped by pg_dump version 16.2 +-- Dumped from database version 15.6 (Debian 15.6-1.pgdg120+2) +-- Dumped by pg_dump version 15.6 (Debian 15.6-0+deb12u1) SET statement_timeout = 0; SET lock_timeout = 0; @@ -61,6 +61,17 @@ SET default_tablespace = ''; SET default_table_access_method = heap; +-- +-- Name: alembic_version; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.alembic_version ( + version_num character varying(32) NOT NULL +); + + +ALTER TABLE public.alembic_version OWNER TO postgres; + -- -- Name: card; Type: TABLE; Schema: public; Owner: postgres -- @@ -289,6 +300,15 @@ ALTER TABLE public.quiz ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( ); +-- +-- Data for Name: alembic_version; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.alembic_version (version_num) FROM stdin; +0f17df0ee1a8 +\. + + -- -- Data for Name: card; Type: TABLE DATA; Schema: public; Owner: postgres -- @@ -412,6 +432,14 @@ SELECT pg_catalog.setval('public.member_data_id_seq', 7, true); SELECT pg_catalog.setval('public.quiz_id_seq', 1, true); +-- +-- Name: alembic_version alembic_version_pkc; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.alembic_version + ADD CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num); + + -- -- Name: card card_card_serial_key; Type: CONSTRAINT; Schema: public; Owner: postgres --