Skip to content

Commit

Permalink
Merge pull request #30 from Jim-Hodapp-Coaching/create_mvp_db_schema_…
Browse files Browse the repository at this point in the history
…using_dbml

Create and document an MVP DB schema using DBML, auto-generate the DB with a new script, and update the existing entities with the schema changes.
  • Loading branch information
jhodapp committed Feb 18, 2024
2 parents a5a1e3a + cf8fe3d commit 59878fc
Show file tree
Hide file tree
Showing 32 changed files with 817 additions and 209 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/target
dbml-error.log
10 changes: 7 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 62 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,86 @@ The platform itself is useful for professional independent coaches, informal men

## Basic Local DB Setup and Management

### Set Up Database
## Running the Database Setup Script

Note: these are commands meant to run against a real Postgresql server.
1. Ensure you have PostgreSQL installed and running on your machine. If you're using macOS, you can use
[Postgres.app](https://postgresapp.com/) or install it with Homebrew:

```shell
brew install postgresql
```

2. Make sure you have the `dbml2sql` tool installed. You can install it with npm:

```shell
npm install -g @dbml/cli
```

3. Run the script with default settings:

```shell
./scripts/rebuild_db.sh
```

This will create a database named `refactor_platform`, a user named `refactor`, and a schema named `refactor_platform`.

4. If you want to use different settings, you can provide them as arguments to the script:

```shell
./scripts/rebuild_db.sh my_database my_user my_schema
```

This will create a database named `my_database`, a user named `my_user`, and a schema named `my_schema`.

Please note that the script assumes that the password for the new PostgreSQL user is `password`. If you want to use a different password, you'll need to modify the script accordingly.
### Set Up Database Manually
Note: these are commands meant to run against a real Postgresql server with an admin level user.
```sql
--create user
CREATE USER refactor_rs WITH PASSWORD 'password';
--create schema
CREATE SCHEMA IF NOT EXISTS refactor_platform_rs;
--Check to see that the schema exists
SELECT schema_name FROM information_schema.schemata;
--Grant schema access to user
GRANT CREATE ON SCHEMA public TO refactor_rs;
--create new database `refactor_platform`
CREATE DATABASE refactor_platform;
```
### Generate a New Migration
```bash
sea-orm-cli migrate generate your_table_name
Change to the refactor_platform DB visually if using app like Postico, otherwise change using the
Postgresql CLI:
```sh
\c refactor_platform
```
```sql
--create new database user `refactor`
CREATE USER refactor WITH PASSWORD 'password';
--create a new schema owned by user `refactor`
CREATE SCHEMA IF NOT EXISTS refactor_platform AUTHORIZATION refactor;
--Check to see that the schema `refactor_platform` exists in the results
SELECT schema_name FROM information_schema.schemata;
--Grant all privileges on schema `refactor_platform` to user `refactor`
GRANT ALL PRIVILEGES ON SCHEMA refactor_platform TO refactor;
```
### Run Migrations
Note: this assumes a database name of `refactor_platform_rs`
Note: this assumes a database name of `refactor_platform`
```bash
DATABASE_URL=postgres://refactor_rs:password@localhost:5432/refactor_platform_rs sea-orm-cli migrate up -s refactor_platform_rs
DATABASE_URL=postgres://refactor:password@localhost:5432/refactor_platform sea-orm-cli migrate up -s refactor_platform
```
### Generate Entity from Database
### Generate a new Entity from Database
Note that to generate a new Entity using the CLI you must ignore all other tables using the `--ignore-tables` option. You must add the option for _each_ table you are ignoring.
```bash
DATABASE_URL=postgres://refactor_rs:password@localhost:5432/refactor_platform_rs sea-orm-cli generate entity -s refactor_platform_rs -o entity/src
DATABASE_URL=postgres://refactor:password@localhost:5432/refactor_platform sea-orm-cli generate entity -s refactor_platform -o entity/src -v --with-serde both --serde-skip-deserializing-primary-key --ignore-tables {table to ignore} --ignore-tables {other table to ignore}
```
## Project Directory Structure
`docs` - project documentation including architectural records, DB schema, API docs, etc
`entity_api` - data operations on the various `Entity` models
`entity` - shape of the data models and the relationships to each other
Expand Down
114 changes: 114 additions & 0 deletions docs/db/refactor_platform_rs.dbml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Use DBML to define your database structure
// Docs: https://dbml.dbdiagram.io/docs

Table refactor_platform.organizations {
id integer [primary key, unique, not null, increment]
external_id uuid [unique, not null, default: `gen_random_uuid()`, note: 'The publicly visible identifier for a record']
name varchar [note: 'The name of the organization that the coach <--> coachee belong to']
logo varchar [note: 'A URI pointing to the organization\'s logo icon file']
created_at timestamptz [default: `now()`]
updated_at timestamptz [default: `now()`, note: 'The last date and time fields were changed']
}

// Coaching relationship type belonging to the refactor_platform schema
// from the perspective of the coach
Table refactor_platform.coaching_relationships {
id integer [primary key, unique, not null, increment]
external_id uuid [unique, not null, default: `gen_random_uuid()`, note: 'The publicly visible identifier for a record']
organization_id integer [not null, note: 'The organization associated with this coaching relationship']
coach_id integer [not null, note: 'The coach associated with this coaching relationship']
coachee_id integer [not null, note: 'The coachee associated with this coaching relationship']
created_at timestamptz [default: `now()`]
updated_at timestamptz [default: `now()`, note: 'The last date and time fields were changed']
}

Table refactor_platform.users {
id integer [primary key, unique, not null, increment]
external_id uuid [unique, not null, default: `gen_random_uuid()`, note: 'The publicly visible identifier for a record']
email varchar [unique, not null]
first_name varchar
last_name varchar
display_name varchar [note: 'If a user wants to go by something other than first & last names']
password varchar
github_username varchar // Specifically GH for now, can generalize later
github_profile_url varchar
created_at timestamptz [default: `now()`]
updated_at timestamptz [default: `now()`, note: 'The last date and time fields were changed']
}

Table refactor_platform.coaching_sessions {
id integer [primary key, unique, not null, increment]
external_id uuid [unique, not null, default: `gen_random_uuid()`, note: 'The publicly visible identifier for a record']
coaching_relationship_id integer [not null, note: 'The coaching relationship (i.e. what coach & coachee under what organization) associated with this coaching session']
date timestamp [note: 'The date and time of a session']
timezone varchar [note: 'The baseline timezone used for the `date` field']
created_at timestamptz [default: `now()`]
updated_at timestamptz [default: `now()`, note: 'The last date and time fields were changed']
}

Table refactor_platform.overarching_goals {
id integer [primary key, unique, not null, increment]
external_id uuid [unique, not null, default: `gen_random_uuid()`, note: 'The publicly visible identifier for a record']
coaching_session_id integer [note: 'The coaching session that an overarching goal is associated with']
title varchar [note: 'A short description of an overarching goal']
details varchar [note: 'A long description of an overarching goal']
completed_at timestamptz [note: 'The date and time an overarching goal was completed']
created_at timestamptz [default: `now()`]
updated_at timestamptz [default: `now()`, note: 'The last date and time fields were changed']
}

Table refactor_platform.notes {
id integer [primary key, unique, not null, increment]
external_id uuid [unique, not null, default: `gen_random_uuid()`, note: 'The publicly visible identifier for a record']
coaching_session_id integer [not null]
body varchar [note: 'Main text of the note supporting Markdown']
user_id integer [not null, note: 'User that created (owns) the note']
created_at timestamptz [default: `now()`]
updated_at timestamptz [default: `now()`, note: 'The last date and time an overarching note\'s fields were changed']
}

Table refactor_platform.agreements {
id integer [primary key, unique, not null, increment]
external_id uuid [unique, not null, default: `gen_random_uuid()`, note: 'The publicly visible identifier for a record']
coaching_session_id integer [not null]
details varchar [note: 'Either a short or long description of an agreement reached between coach and coachee in a coaching session']
user_id integer [not null, note: 'User that created (owns) the agreement']
created_at timestamptz [default: `now()`]
updated_at timestamptz [default: `now()`, note: 'The last date and time an overarching agreement\'s fields were changed']
}

Table refactor_platform.actions {
id integer [primary key, unique, not null, increment]
external_id uuid [unique, not null, default: `gen_random_uuid()`, note: 'The publicly visible identifier for a record']
// The first session where this action was created
// It will carry forward to every future session until
// its due_by is passed or it was completed by the coachee
coaching_session_id integer [not null]
due_by timestamptz
completed boolean // May be unnecessary if there's a valid completed_at timestamp
completed_at timestamptz
created_at timestamp [default: `now()`]
updated_at timestamp [default: `now()`]
}

// coaching_relationships relationships
Ref: refactor_platform.coaching_relationships.organization_id > refactor_platform.organizations.id
Ref: refactor_platform.coaching_relationships.coachee_id > refactor_platform.users.id
Ref: refactor_platform.coaching_relationships.coach_id > refactor_platform.users.id

// coaching_sessions relationships
Ref: refactor_platform.coaching_sessions.coaching_relationship_id > refactor_platform.coaching_relationships.id

// overarching_goals relationships
Ref: refactor_platform.overarching_goals.coaching_session_id > refactor_platform.coaching_sessions.id

// notes relationships
Ref: refactor_platform.notes.coaching_session_id > refactor_platform.coaching_sessions.id
Ref: refactor_platform.notes.user_id > refactor_platform.users.id

// agreements relationships
Ref: refactor_platform.agreements.coaching_session_id > refactor_platform.coaching_sessions.id
Ref: refactor_platform.agreements.user_id > refactor_platform.users.id

// actions relationships
Ref: refactor_platform.actions.coaching_session_id > refactor_platform.coaching_sessions.id
4 changes: 3 additions & 1 deletion entity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ path = "src/lib.rs"

[dependencies]
axum-login = "0.12.0"
chrono = { version = "0.4.34", features = ["serde"] }
serde = { version = "1", features = ["derive"] }

uuid = "1.7.0"

[dependencies.sea-orm]
version = "0.12"
features = [ "with-uuid" ]
6 changes: 1 addition & 5 deletions entity/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
## Entity Schema Diagram - Definitions and Relationships

![refactor_entity_schema](https://github.com/Jim-Hodapp-Coaching/refactor-platform-rs/assets/3219120/1656ee0f-da18-41fb-9472-379fcca29500)

## Example Data - A Journey with Multiple Steps

![refactor_entity_example_multiple_journey_steps](https://github.com/Jim-Hodapp-Coaching/refactor-platform-rs/assets/3219120/e933a0f5-8651-4638-8d72-e4903b54d026)
![refactor_entity_schema](https://github.com/Jim-Hodapp-Coaching/refactor-platform-rs/assets/3219120/d8e25a4d-376e-40aa-99de-532f8be06fb0)

## Example Data - A User as a Coach and Coachee in Two Different Organizations

Expand Down
40 changes: 40 additions & 0 deletions entity/src/actions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3

use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(schema_name = "refactor_platform", table_name = "actions")]
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32,
#[sea_orm(unique)]
pub external_id: Uuid,
pub coaching_session_id: i32,
pub due_by: Option<DateTimeWithTimeZone>,
pub completed: Option<bool>,
pub completed_at: Option<DateTimeWithTimeZone>,
pub created_at: Option<DateTime>,
pub updated_at: Option<DateTime>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::coaching_sessions::Entity",
from = "Column::CoachingSessionId",
to = "super::coaching_sessions::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
CoachingSessions,
}

impl Related<super::coaching_sessions::Entity> for Entity {
fn to() -> RelationDef {
Relation::CoachingSessions.def()
}
}

impl ActiveModelBehavior for ActiveModel {}
53 changes: 53 additions & 0 deletions entity/src/agreements.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3

use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(schema_name = "refactor_platform", table_name = "agreements")]
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32,
#[sea_orm(unique)]
pub external_id: Uuid,
pub coaching_session_id: i32,
pub details: Option<String>,
pub user_id: i32,
pub created_at: Option<DateTimeWithTimeZone>,
pub updated_at: Option<DateTimeWithTimeZone>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::coaching_sessions::Entity",
from = "Column::CoachingSessionId",
to = "super::coaching_sessions::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
CoachingSessions,
#[sea_orm(
belongs_to = "super::users::Entity",
from = "Column::UserId",
to = "super::users::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
Users,
}

impl Related<super::coaching_sessions::Entity> for Entity {
fn to() -> RelationDef {
Relation::CoachingSessions.def()
}
}

impl Related<super::users::Entity> for Entity {
fn to() -> RelationDef {
Relation::Users.def()
}
}

impl ActiveModelBehavior for ActiveModel {}
5 changes: 3 additions & 2 deletions entity/src/coaching_relationship.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3

use crate::Id;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize, Serialize)]
#[sea_orm(
schema_name = "refactor_platform_rs",
schema_name = "refactor_platform",
table_name = "coaching_relationships"
)]
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32,
pub id: Id,
pub coachee_id: String,
pub coach_id: String,
pub organization_id: String,
Expand Down
Loading

0 comments on commit 59878fc

Please sign in to comment.