Add client dir, move .git from server to parent dir

This commit is contained in:
D. Scott Boggs 2023-06-11 07:01:57 -04:00
parent b8b0e51046
commit f794f5c974
41 changed files with 757 additions and 7 deletions

1
server/.dockerignore Normal file
View file

@ -0,0 +1 @@
**/target

5
server/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"cSpell.words": [
"sqlx"
]
}

2893
server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

44
server/Cargo.toml Normal file
View file

@ -0,0 +1,44 @@
[package]
name = "kalkulog-server"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "kalkulog-server"
path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sea-orm-migration = "0.11.3"
serde_json = "1.0.96"
thiserror = "1.0.40"
[dependencies.derive_builder]
version = "0.12.0"
features = ["clippy"]
[dependencies.tokio]
version = "1.28.1"
features = ["full"]
[dependencies.sea-orm]
version = "^0"
features = [
"sqlx-postgres",
"runtime-tokio-rustls",
"macros",
"with-time"
]
[dependencies.rocket]
git = "https://github.com/SergioBenitez/Rocket"
rev = "v0.5.0-rc.3"
features = ["json"]
[dependencies.serde]
version = "1.0.163"
features = ["derive"]
[dependencies.either]
version = "1.8.1"
features = ["serde"]

11
server/Dockerfile Normal file
View file

@ -0,0 +1,11 @@
FROM rustlang/rust:nightly-bullseye-slim
WORKDIR /src
ADD Cargo.toml Cargo.lock /src/
RUN echo "fn main() {}" > dummy.rs &&\
sed -i "s:src/main.rs:dummy.rs:" Cargo.toml
RUN cargo build --release
ADD src/ src/
RUN rm dummy.rs &&\
sed -i "s:dummy.rs:src/main.rs:" Cargo.toml
RUN cargo build --release
CMD ["target/release/kalkulog-server"]

31
server/src/api/error.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::error::Error;
#[derive(Responder)]
#[response(status = 500, content_type = "json")]
pub(crate) struct ErrorResponder {
message: String,
}
pub(crate) type ApiResult<T> = Result<T, ErrorResponder>;
// The following impl's are for easy conversion of error types.
impl From<Error> for ErrorResponder {
fn from(err: Error) -> ErrorResponder {
ErrorResponder {
message: err.to_string(),
}
}
}
impl From<String> for ErrorResponder {
fn from(string: String) -> ErrorResponder {
ErrorResponder { message: string }
}
}
impl From<&str> for ErrorResponder {
fn from(str: &str) -> ErrorResponder {
str.to_owned().into()
}
}

71
server/src/api/groups.rs Normal file
View file

@ -0,0 +1,71 @@
use either::{Either, Left, Right};
use rocket::{http::Status, serde::json::Json, State};
use sea_orm::{prelude::*, DatabaseConnection};
use crate::{
entities::{prelude::*, *},
error::Error,
};
use super::error::ApiResult;
#[get("/")]
pub(super) async fn all_groups(
db: &State<DatabaseConnection>,
) -> ApiResult<Json<Vec<groups::Model>>> {
Ok(Json(
Groups::find()
.all(db as &DatabaseConnection)
.await
.map_err(Error::from)?,
))
}
#[get("/<id>")]
pub(super) async fn group(
db: &State<DatabaseConnection>,
id: i32,
) -> Result<Json<groups::Model>, Either<Status, super::ErrorResponder>> {
match Groups::find_by_id(id).one(db as &DatabaseConnection).await {
Ok(Some(group)) => Ok(Json(group)),
Ok(None) => Err(Left(Status::NotFound)),
Err(err) => Err(Right(Error::from(err).into())),
}
}
#[post("/", format = "application/json", data = "<group>")]
pub(super) async fn insert_group(
db: &State<DatabaseConnection>,
group: Json<serde_json::Value>,
) -> ApiResult<Json<groups::Model>> {
Ok(Json(
groups::ActiveModel::from_json(group.0)
.map_err(Error::from)?
.insert(db as &DatabaseConnection)
.await
.map_err(Error::from)?,
))
}
#[put("/", format = "application/json", data = "<group>")]
pub(super) async fn update_group(
db: &State<DatabaseConnection>,
group: Json<serde_json::Value>,
) -> ApiResult<Json<groups::Model>> {
Ok(Json(
groups::ActiveModel::from_json(group.0)
.map_err(Error::from)?
.update(db as &DatabaseConnection)
.await
.map_err(Error::from)?,
))
}
#[delete("/<id>")]
pub(super) async fn delete_group(db: &State<DatabaseConnection>, id: i32) -> ApiResult<Status> {
Groups::delete_by_id(id)
.exec(db as &DatabaseConnection)
.await
.map_err(Error::from)?;
Ok(Status::Ok)
}

44
server/src/api/mod.rs Normal file
View file

@ -0,0 +1,44 @@
mod error;
mod groups;
mod ticks;
mod tracks;
use std::default::default;
use std::net::{IpAddr, Ipv4Addr};
use rocket::Config;
use sea_orm::DatabaseConnection;
use crate::rocket::{Build, Rocket};
pub(crate) use error::ErrorResponder;
#[get("/status")]
fn status() -> &'static str {
"Ok"
}
pub(crate) fn start_server(db: DatabaseConnection) -> Rocket<Build> {
use groups::*;
use ticks::*;
use tracks::*;
rocket::build()
.configure(Config {
address: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
..default()
})
.manage(db)
.mount("/api/v1", routes![status])
.mount(
"/api/v1/tracks",
routes![all_tracks, track, insert_track, update_track, delete_track],
)
.mount(
"/api/v1/ticks",
routes![all_ticks, tick, insert_tick, update_tick, delete_tick],
)
.mount(
"/api/v1/groups",
routes![all_groups, group, insert_group, update_group, delete_group],
)
}

68
server/src/api/ticks.rs Normal file
View file

@ -0,0 +1,68 @@
use either::{Either, Left, Right};
use rocket::{http::Status, serde::json::Json, State};
use sea_orm::{prelude::*, DatabaseConnection};
use crate::{
entities::{prelude::*, *},
error::Error,
};
use super::error::ApiResult;
#[get("/")]
pub(super) async fn all_ticks(
db: &State<DatabaseConnection>,
) -> ApiResult<Json<Vec<ticks::Model>>> {
let db = db as &DatabaseConnection;
let ticks = Ticks::find().all(db).await.map_err(Error::from)?;
Ok(Json(ticks))
}
#[get("/<id>")]
pub(super) async fn tick(
db: &State<DatabaseConnection>,
id: i32,
) -> Result<Json<ticks::Model>, Either<Status, super::ErrorResponder>> {
match Ticks::find_by_id(id).one(db as &DatabaseConnection).await {
Ok(Some(tick)) => Ok(Json(tick)),
Ok(None) => Err(Left(Status::NotFound)),
Err(err) => Err(Right(Error::from(err).into())),
}
}
#[post("/", format = "application/json", data = "<tick>")]
pub(super) async fn insert_tick(
db: &State<DatabaseConnection>,
tick: Json<serde_json::Value>,
) -> ApiResult<Json<ticks::Model>> {
Ok(Json(
ticks::ActiveModel::from_json(tick.0)
.map_err(Error::from)?
.insert(db as &DatabaseConnection)
.await
.map_err(Error::from)?,
))
}
#[put("/", format = "application/json", data = "<tick>")]
pub(super) async fn update_tick(
db: &State<DatabaseConnection>,
tick: Json<serde_json::Value>,
) -> ApiResult<Json<ticks::Model>> {
Ok(Json(
ticks::ActiveModel::from_json(tick.0)
.map_err(Error::from)?
.update(db as &DatabaseConnection)
.await
.map_err(Error::from)?,
))
}
#[delete("/<id>")]
pub(super) async fn delete_tick(db: &State<DatabaseConnection>, id: i32) -> ApiResult<Status> {
Tracks::delete_by_id(id)
.exec(db as &DatabaseConnection)
.await
.map_err(Error::from)?;
Ok(Status::Ok)
}

69
server/src/api/tracks.rs Normal file
View file

@ -0,0 +1,69 @@
use crate::api::{self, error::ApiResult};
use crate::entities::{prelude::*, *};
use crate::error::Error;
use either::Either::{self, Left, Right};
use rocket::http::Status;
use rocket::{serde::json::Json, State};
use sea_orm::{prelude::*, DatabaseConnection};
use std::default::default;
#[get("/")]
pub(super) async fn all_tracks(
db: &State<DatabaseConnection>,
) -> ApiResult<Json<Vec<tracks::Model>>> {
let db = db as &DatabaseConnection;
let tracks = Tracks::find().all(db).await.unwrap();
Ok(Json(tracks))
}
#[get("/<id>")]
pub(super) async fn track(
db: &State<DatabaseConnection>,
id: i32,
) -> Result<Json<tracks::Model>, Either<Status, api::ErrorResponder>> {
let db = db as &DatabaseConnection;
match Tracks::find_by_id(id).one(db).await {
Ok(Some(track)) => Ok(Json(track)),
Ok(None) => Err(Left(Status::NotFound)),
Err(err) => Err(Right(Error::from(err).into())),
}
}
#[post("/", format = "application/json", data = "<track>")]
pub(super) async fn insert_track(
db: &State<DatabaseConnection>,
track: Json<serde_json::Value>,
) -> ApiResult<Json<tracks::Model>> {
let mut track = track.0;
let db = db as &DatabaseConnection;
let mut model: tracks::ActiveModel = default();
track["id"] = 0.into(); // dummy value. set_from_json doesn't use this value
// but for some reason requires it be set
model.set_from_json(track).map_err(Error::from)?;
Ok(Json(model.insert(db).await.map_err(Error::from)?))
}
#[put("/", format = "application/json", data = "<track>")]
pub(super) async fn update_track(
db: &State<DatabaseConnection>,
track: Json<serde_json::Value>,
) -> ApiResult<Json<tracks::Model>> {
let db = db as &DatabaseConnection;
Ok(Json(
tracks::ActiveModel::from_json(track.0)
.map_err(Error::from)?
.update(db)
.await
.map_err(Error::from)?,
))
}
#[delete("/<id>")]
pub(super) async fn delete_track(db: &State<DatabaseConnection>, id: i32) -> ApiResult<Status> {
let db = db as &DatabaseConnection;
Tracks::delete_by_id(id)
.exec(db)
.await
.map_err(Error::from)?;
Ok(Status::Ok)
}

60
server/src/db/mod.rs Normal file
View file

@ -0,0 +1,60 @@
use std::{
default::default,
env,
ffi::{OsStr, OsString},
fs::File,
io::Read,
};
// from https://doc.rust-lang.org/std/ffi/struct.OsString.html
fn concat_os_strings(a: &OsStr, b: &OsStr) -> OsString {
let mut ret = OsString::with_capacity(a.len() + b.len()); // This will allocate
ret.push(a); // This will not allocate further
ret.push(b); // This will not allocate further
ret
}
/// Check for an environment variable named for the given key with _FILE
/// appended to the end. If that exists, return the contents of the file, or
/// panic if it doesn't exist. If the `"${key}_FILE"` environment variable is
/// not set, return the value of the environment variable set by the given key,
/// or `None` if it's not set.
///
/// Panics:
/// - if the given file variable doesn't exist
/// - if there's an error reading the specified file
/// - if the environment variable string contains invalid unicode.
fn get_env_var_or_file<A: AsRef<OsStr>>(key: A) -> Option<String> {
let key = key.as_ref();
let file_key = concat_os_strings(key, "_FILE".as_ref());
if let Some(path) = env::var_os(file_key) {
// open the file and read it
let mut file = File::open(&path).unwrap_or_else(|_| panic!("no such file at {path:?}"));
let mut val: String = default();
file.read_to_string(&mut val)
.unwrap_or_else(|_| panic!("reading file at {path:?}"));
Some(val)
} else {
env::var_os(key).map(|val| {
val.to_str()
.expect(&format!("value for ${key:?} contains invalid unicode"))
.to_string()
})
}
}
/// Connect to the database using environment variables for configuration.
/// Panics on any failure.
pub(crate) fn connection_url() -> String {
let user = get_env_var_or_file("POSTGRES_USER").expect("$POSTGRES_USER");
let password = get_env_var_or_file("POSTGRES_PASSWORD").expect("$POSTGRES_PASSWORD");
let db = get_env_var_or_file("POSTGRES_DB").expect("$POSTGRES_DB");
let host = get_env_var_or_file("POSTGRES_HOST").unwrap_or_else(|| "localhost".into());
let port = get_env_var_or_file("POSTGRES_PORT")
.map(|port| {
port.parse::<u16>()
.expect("$POSTGRES_PORT is not a valid port number")
})
.unwrap_or(5432_u16);
format!("postgres://{user}:{password}@{host}:{port}/{db}")
}

View file

@ -0,0 +1,28 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "groups")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub description: String,
pub order: Option<i32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::track2_groups::Entity")]
Track2Groups,
}
impl Related<super::track2_groups::Entity> for Entity {
fn to() -> RelationDef {
Relation::Track2Groups.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,8 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
pub mod prelude;
pub mod groups;
pub mod ticks;
pub mod track2_groups;
pub mod tracks;

View file

@ -0,0 +1,6 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
pub use super::groups::Entity as Groups;
pub use super::ticks::Entity as Ticks;
pub use super::track2_groups::Entity as Track2Groups;
pub use super::tracks::Entity as Tracks;

View file

@ -0,0 +1,39 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "ticks")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub track_id: Option<i32>,
pub year: Option<i32>,
pub month: Option<i32>,
pub day: Option<i32>,
pub hour: Option<i32>,
pub minute: Option<i32>,
pub second: Option<i32>,
pub has_time_info: Option<i32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::tracks::Entity",
from = "Column::TrackId",
to = "super::tracks::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
Tracks,
}
impl Related<super::tracks::Entity> for Entity {
fn to() -> RelationDef {
Relation::Tracks.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,46 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "track2_groups")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub track_id: i32,
pub group_id: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::groups::Entity",
from = "Column::GroupId",
to = "super::groups::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
Groups,
#[sea_orm(
belongs_to = "super::tracks::Entity",
from = "Column::TrackId",
to = "super::tracks::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
Tracks,
}
impl Related<super::groups::Entity> for Entity {
fn to() -> RelationDef {
Relation::Groups.def()
}
}
impl Related<super::tracks::Entity> for Entity {
fn to() -> RelationDef {
Relation::Tracks.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -0,0 +1,40 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "tracks")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub description: String,
pub icon: String,
pub enabled: i32,
pub multiple_entries_per_day: Option<i32>,
pub color: Option<i32>,
pub order: Option<i32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::ticks::Entity")]
Ticks,
#[sea_orm(has_many = "super::track2_groups::Entity")]
Track2Groups,
}
impl Related<super::ticks::Entity> for Entity {
fn to() -> RelationDef {
Relation::Ticks.def()
}
}
impl Related<super::track2_groups::Entity> for Entity {
fn to() -> RelationDef {
Relation::Track2Groups.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

11
server/src/error.rs Normal file
View file

@ -0,0 +1,11 @@
use derive_builder::UninitializedFieldError;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Builder(#[from] UninitializedFieldError),
#[error(transparent)]
SeaOrm(#[from] sea_orm::DbErr),
}
pub type Result<T> = std::result::Result<T, Error>;

36
server/src/main.rs Normal file
View file

@ -0,0 +1,36 @@
#![feature(default_free_fn, proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
mod api;
mod db;
mod entities;
mod error;
mod migrator;
use crate::migrator::Migrator;
use sea_orm::Database;
use sea_orm_migration::prelude::*;
#[launch]
async fn rocket_defines_the_main_fn() -> _ {
let url = db::connection_url();
let db = Database::connect(url).await.expect("db connection");
let schema_manager = SchemaManager::new(&db);
Migrator::refresh(&db).await.expect("migration");
assert!(schema_manager
.has_table("tracks")
.await
.expect("fetch tracks table"));
assert!(schema_manager
.has_table("ticks")
.await
.expect("fetch ticks table"));
assert!(schema_manager
.has_table("groups")
.await
.expect("fetch groups table"));
assert!(schema_manager
.has_table("track2_groups")
.await
.expect("fetch track2groups table"));
api::start_server(db)
}

View file

@ -0,0 +1,58 @@
use sea_orm_migration::{async_trait::async_trait, prelude::*};
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20230606_000001_create_tracks_table"
}
}
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Tracks::Table)
.col(
ColumnDef::new(Tracks::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Tracks::Name).string().not_null())
.col(ColumnDef::new(Tracks::Description).string().not_null())
.col(ColumnDef::new(Tracks::Icon).string().not_null())
.col(ColumnDef::new(Tracks::Enabled).integer().not_null())
.col(
ColumnDef::new(Tracks::MultipleEntriesPerDay)
.integer()
.default(0),
)
.col(ColumnDef::new(Tracks::Color).integer().default(0))
.col(ColumnDef::new(Tracks::Order).integer().default(-1))
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Tracks::Table).to_owned())
.await
}
}
#[derive(Iden)]
pub enum Tracks {
Table,
Id,
Name,
Description,
Icon,
Enabled,
MultipleEntriesPerDay,
Color,
Order,
}

View file

@ -0,0 +1,63 @@
use super::m20230606_000001_create_tracks_table::Tracks;
use sea_orm_migration::{async_trait::async_trait, prelude::*};
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20230606_000002_create_ticks_table"
}
}
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Ticks::Table)
.col(
ColumnDef::new(Ticks::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Ticks::TrackId).integer())
.col(ColumnDef::new(Ticks::Year).integer())
.col(ColumnDef::new(Ticks::Month).integer())
.col(ColumnDef::new(Ticks::Day).integer())
.col(ColumnDef::new(Ticks::Hour).integer())
.col(ColumnDef::new(Ticks::Minute).integer())
.col(ColumnDef::new(Ticks::Second).integer())
.col(ColumnDef::new(Ticks::HasTimeInfo).integer().default(0))
.foreign_key(
ForeignKey::create()
.name("fk-ticks-track_id")
.from(Ticks::Table, Ticks::TrackId)
.to(Tracks::Table, Tracks::Id),
)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Ticks::Table).to_owned())
.await
}
}
#[derive(Iden)]
pub enum Ticks {
Table,
Id,
TrackId,
Year,
Month,
Day,
Hour,
Minute,
Second,
HasTimeInfo,
}

View file

@ -0,0 +1,46 @@
use sea_orm_migration::{async_trait::async_trait, prelude::*};
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20230606_000003_create_groups_table"
}
}
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Groups::Table)
.col(
ColumnDef::new(Groups::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Groups::Name).string().not_null())
.col(ColumnDef::new(Groups::Description).string().not_null())
.col(ColumnDef::new(Groups::Order).integer().default(-1))
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Groups::Table).to_owned())
.await
}
}
#[derive(Iden)]
pub enum Groups {
Table,
Id,
Name,
Description,
Order,
}

View file

@ -0,0 +1,61 @@
use sea_orm_migration::{async_trait::async_trait, prelude::*};
use super::{
m20230606_000001_create_tracks_table::Tracks, m20230606_000003_create_groups_table::Groups,
};
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20230606_000001_create_track2groups_table"
}
}
#[async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Track2Groups::Table)
.col(
ColumnDef::new(Track2Groups::Id)
.integer()
.not_null()
.primary_key()
.auto_increment(),
)
.col(ColumnDef::new(Track2Groups::TrackId).integer().not_null())
.col(ColumnDef::new(Track2Groups::GroupId).integer().not_null())
.foreign_key(
ForeignKey::create()
.name("fk-track2groups-track_id")
.from(Track2Groups::Table, Track2Groups::TrackId)
.to(Tracks::Table, Tracks::Id),
)
.foreign_key(
ForeignKey::create()
.name("fk-track2groups-group_id")
.from(Track2Groups::Table, Track2Groups::GroupId)
.to(Groups::Table, Groups::Id),
)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Track2Groups::Table).to_owned())
.await
}
}
#[derive(Iden)]
pub enum Track2Groups {
Table,
Id,
TrackId,
GroupId,
}

View file

@ -0,0 +1,20 @@
mod m20230606_000001_create_tracks_table;
mod m20230606_000002_create_ticks_table;
mod m20230606_000003_create_groups_table;
mod m20230606_000004_create_track2groups_table;
use sea_orm_migration::prelude::*;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20230606_000001_create_tracks_table::Migration),
Box::new(m20230606_000002_create_ticks_table::Migration),
Box::new(m20230606_000003_create_groups_table::Migration),
Box::new(m20230606_000004_create_track2groups_table::Migration),
]
}
}