Skip to content

Create and document an MVP DB schema using DBML #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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