creating an axum rest api app and integrating with authjs through adaptor
passwords? where we’re going, we dont need passwords.
toc
create rest api axum app with loco:
cargo install loco-clicargo install sea-orm-cli
loco new✔ ❯ App name? · myapp? ❯ What would you like to build? › lightweight-service (minimal, only controllers and views)❯ Rest API (with DB and user auth) SaaS app (with DB and user auth)
creating pg-adaptor tables
https://authjs.dev/reference/pg-adapter
we need to following tables:
CREATE TABLE verification_token ( identifier TEXT NOT NULL, expires TIMESTAMPTZ NOT NULL, token TEXT NOT NULL,
PRIMARY KEY (identifier, token));
CREATE TABLE accounts ( id SERIAL, "userId" INTEGER NOT NULL, type VARCHAR(255) NOT NULL, provider VARCHAR(255) NOT NULL, "providerAccountId" VARCHAR(255) NOT NULL, refresh_token TEXT, access_token TEXT, expires_at BIGINT, id_token TEXT, scope TEXT, session_state TEXT, token_type TEXT,
PRIMARY KEY (id));
CREATE TABLE sessions ( id SERIAL, "userId" INTEGER NOT NULL, expires TIMESTAMPTZ NOT NULL, "sessionToken" VARCHAR(255) NOT NULL,
PRIMARY KEY (id));
CREATE TABLE users ( id SERIAL, name VARCHAR(255), email VARCHAR(255), "emailVerified" TIMESTAMPTZ, image TEXT,
PRIMARY KEY (id));
create migrations with loco:
the rest api template app from loco already created a users table. we need to update that, but first let’s create the verification_token, accounts and sessions tables. add the --migration-only
flag to not migrate the db and create the rust entity because we need to do some modifications before migrating:
cargo loco generate model verification_token identifier:string! token:string! expires:tstz! --migration-only
by default loco creates created_at
and updated_at
timestamps for models
use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]pub struct Migration;
#[async_trait::async_trait]impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( // 👇 the table_auto command creates timestamps fields table_auto(VerificationTokens::Table) .col(pk_auto(VerificationTokens::Id)) .col(string(VerificationTokens::Identifier)) .col(string(VerificationTokens::Token)) .col(timestamp_with_time_zone(VerificationTokens::Expires)) .to_owned(), ) .await }
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table(VerificationTokens::Table).to_owned()) .await }}
#[derive(DeriveIden)]enum VerificationTokens { Table, Id, Identifier, Token, Expires,
}
lets update the default migration to remove the created_at
and updated_at
timestamps and create the composite primary key on primary_key and token:
diff --git a/backend/migration/src/m20240508_101645_verification_tokens.rs b/backend/migration/src/m20240508_101645_verification_tokens.rsindex ebaca79..850901e 100644--- a/backend/migration/src/m20240508_101645_verification_tokens.rs+++ b/backend/migration/src/m20240508_101645_verification_tokens.rs@@ -8,8 +8,14 @@ impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table(- table_auto(VerificationTokens::Table)- .col(pk_auto(VerificationTokens::Id))+ Table::create()+ .table(VerificationTokens::Table)+ .primary_key(+ Index::create()+ .name("verification_tokens_pkey")+ .col(VerificationTokens::Identifier)+ .col(VerificationTokens::Token),+ ) .col(string(VerificationTokens::Identifier)) .col(string(VerificationTokens::Token)) .col(timestamp_with_time_zone(VerificationTokens::Expires))@@ -28,7 +34,6 @@ impl MigrationTrait for Migration { #[derive(DeriveIden)] enum VerificationTokens { Table,- Id, Identifier, Token, Expires,
run migration
cargo loco db migrate
update our rust models based on the schema
cargo loco db entities
or verification_token entity will now look like this:
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.15
use sea_orm::entity::prelude::*;use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]#[sea_orm(table_name = "verification_tokens")]pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub identifier: String, #[sea_orm(primary_key, auto_increment = false)] pub token: String, pub expires: DateTimeWithTimeZone,}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]pub enum Relation {}
create migration for accounts
cargo loco generate model accounts userId:int! type:string! provider:string! providerAccountId:string! refresh_token:text access_token:text expires_at:big_int id_token:text scope:text session_state:text token_type:text --migration-only
remove created_at
and updated_at
fields:
use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]pub struct Migration;
#[async_trait::async_trait]impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( // table_auto(Accounts::Table) Table::create() .table(Accounts::Table) .col(pk_auto(Accounts::Id)) .col(integer(Accounts::UserId)) .col(string(Accounts::Type)) .col(string(Accounts::Provider)) .col(string(Accounts::ProviderAccountId)) .col(text_null(Accounts::RefreshToken)) .col(text_null(Accounts::AccessToken)) .col(big_integer_null(Accounts::ExpiresAt)) .col(text_null(Accounts::IdToken)) .col(text_null(Accounts::Scope)) .col(text_null(Accounts::SessionState)) .col(text_null(Accounts::TokenType)) .to_owned(), ) .await }
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table(Accounts::Table).to_owned()) .await }}
#[derive(DeriveIden)]enum Accounts { Table, Id, UserId, Type, Provider, ProviderAccountId, RefreshToken, AccessToken, ExpiresAt, IdToken, Scope, SessionState, TokenType,}
create migration for sessions
.
cargo loco generate model sessions userId:int! expires:tstz! sessionToken:string! --migration-only
same drill: migration only, remove table_auto statement from default migration.
use sea_orm_migration::{prelude::*, schema::*};
#[derive(DeriveMigrationName)]pub struct Migration;
#[async_trait::async_trait]impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( // table_auto(Sessions::Table) Table::create() .table(Sessions::Table) .col(pk_auto(Sessions::Id)) .col(integer(Sessions::UserId)) .col(timestamp_with_time_zone(Sessions::Expires)) .col(string(Sessions::SessionToken)) .to_owned(), ) .await }
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table(Sessions::Table).to_owned()) .await }}
#[derive(DeriveIden)]enum Sessions { Table, Id, UserId, Expires, SessionToken,}