Alternative ATProto PDS implementation

prototype models and schema

+410 -742
-21
migrations/2025-05-15-175917_init/down.sql
··· 1 - DROP TABLE repo_seq; 2 - DROP TABLE did_doc; 3 - DROP TABLE account_pref; 4 - DROP TABLE backlink; 5 - DROP TABLE record_blob; 6 - DROP TABLE blob; 7 - DROP TABLE record; 8 - DROP TABLE repo_block; 9 - DROP TABLE repo_root; 10 - DROP TABLE email_token; 11 - DROP TABLE account; 12 - DROP TABLE actor; 13 - DROP TABLE refresh_token; 14 - DROP TABLE invite_code_use; 15 - DROP TABLE invite_code; 16 - DROP TABLE app_password; 17 - DROP TABLE authorization_request; 18 - DROP TABLE device; 19 - DROP TABLE device_account; 20 - DROP TABLE token; 21 - DROP TABLE used_refresh_token;
-295
migrations/2025-05-15-175917_init/up.sql
··· 1 - -- Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/helpers/account.rs 2 - -- blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 - -- Modified for SQLite backend 4 - /* Based heavily on account-manager, did-cache, sequencer, and actor-store migrations 5 - from the canonical TS implementation. */ 6 - 7 - -- account-manager implementation 8 - -- Create App Password Table 9 - CREATE TABLE IF NOT EXISTS app_password ( 10 - did character varying NOT NULL, 11 - name character varying NOT NULL, 12 - "password" character varying NOT NULL, 13 - "createdAt" character varying NOT NULL 14 - ); 15 - 16 - ALTER TABLE ONLY app_password 17 - DROP CONSTRAINT IF EXISTS app_password_pkey; 18 - ALTER TABLE ONLY app_password 19 - ADD CONSTRAINT app_password_pkey PRIMARY KEY (did, name); 20 - 21 - -- Create Invite Code Table 22 - CREATE TABLE IF NOT EXISTS invite_code ( 23 - code character varying PRIMARY KEY, 24 - "availableUses" integer NOT NULL, 25 - disabled smallint NOT NULL DEFAULT 0, 26 - "forAccount" character varying NOT NULL, 27 - "createdBy" character varying NOT NULL, 28 - "createdAt" character varying NOT NULL 29 - ); 30 - CREATE INDEX invite_code_for_user_idx 31 - ON invite_code("forAccount"); 32 - 33 - -- Create Invite Code Use Table 34 - CREATE TABLE IF NOT EXISTS invite_code_use ( 35 - code character varying NOT NULL, 36 - "usedBy" character varying NOT NULL, 37 - "usedAt" character varying NOT NULL 38 - ); 39 - 40 - ALTER TABLE ONLY invite_code_use 41 - DROP CONSTRAINT IF EXISTS invite_code_use_pkey; 42 - ALTER TABLE ONLY invite_code_use 43 - ADD CONSTRAINT invite_code_use_pkey PRIMARY KEY (code, "usedBy"); 44 - 45 - -- Create Refresh Token Table 46 - CREATE TABLE IF NOT EXISTS refresh_token ( 47 - id character varying PRIMARY KEY, 48 - did character varying NOT NULL, 49 - "expiresAt" character varying NOT NULL, 50 - "nextId" character varying, 51 - "appPasswordName" character varying 52 - ); 53 - CREATE INDEX refresh_token_did_idx -- Aids in refresh token cleanup 54 - ON refresh_token(did); 55 - 56 - -- Create Actor Table 57 - CREATE TABLE IF NOT EXISTS actor ( 58 - did character varying PRIMARY KEY, 59 - handle character varying, 60 - "createdAt" character varying NOT NULL, 61 - "takedownRef" character varying, 62 - "deactivatedAt" character varying, 63 - "deleteAfter" character varying 64 - ); 65 - CREATE UNIQUE INDEX actor_handle_lower_idx 66 - ON actor (LOWER(handle)); 67 - CREATE INDEX actor_cursor_idx 68 - ON actor("createdAt", did); 69 - 70 - -- Create Account Table 71 - CREATE TABLE IF NOT EXISTS account ( 72 - did character varying PRIMARY KEY, 73 - email character varying NOT NULL, 74 - "recoveryKey" character varying, -- For storing Bring Your Own Key 75 - "password" character varying NOT NULL, 76 - "createdAt" character varying NOT NULL, 77 - "invitesDisabled" smallint NOT NULL DEFAULT 0, 78 - "emailConfirmedAt" character varying 79 - ); 80 - CREATE UNIQUE INDEX account_email_lower_idx 81 - ON account (LOWER(email)); 82 - CREATE INDEX account_cursor_idx 83 - ON account("createdAt", did); 84 - 85 - -- Create Email Token Table 86 - CREATE TABLE IF NOT EXISTS email_token ( 87 - purpose character varying NOT NULL, 88 - did character varying NOT NULL, 89 - token character varying NOT NULL, 90 - "requestedAt" character varying NOT NULL 91 - ); 92 - ALTER TABLE ONLY email_token 93 - DROP CONSTRAINT IF EXISTS email_token_pkey; 94 - ALTER TABLE ONLY email_token 95 - ADD CONSTRAINT email_token_pkey PRIMARY KEY (purpose, did); 96 - CREATE UNIQUE INDEX email_token_purpose_token_unique 97 - ON email_token (purpose, token); 98 - 99 - 100 - -- actor-store implementation 101 - -- Create Repo Root Table 102 - CREATE TABLE IF NOT EXISTS repo_root ( 103 - did character varying PRIMARY KEY, 104 - cid character varying NOT NULL, 105 - rev character varying NOT NULL, 106 - "indexedAt" character varying NOT NULL 107 - ); 108 - 109 - -- Create Repo Block Table 110 - CREATE TABLE IF NOT EXISTS repo_block ( 111 - cid character varying NOT NULL, 112 - did character varying NOT NULL, 113 - "repoRev" character varying NOT NULL, 114 - size integer NOT NULL, 115 - content bytea NOT NULL 116 - ); 117 - ALTER TABLE ONLY repo_block 118 - ADD CONSTRAINT repo_block_pkey PRIMARY KEY (cid, did); 119 - CREATE INDEX repo_block_repo_rev_idx 120 - ON repo_block("repoRev", cid); 121 - 122 - -- Create Record Table 123 - CREATE TABLE IF NOT EXISTS record ( 124 - uri character varying PRIMARY KEY, 125 - cid character varying NOT NULL, 126 - did character varying NOT NULL, 127 - collection character varying NOT NULL, 128 - "rkey" character varying NOT NULL, 129 - "repoRev" character varying, 130 - "indexedAt" character varying NOT NULL, 131 - "takedownRef" character varying 132 - ); 133 - CREATE INDEX record_did_cid_idx 134 - ON record(cid); 135 - CREATE INDEX record_did_collection_idx 136 - ON record(collection); 137 - CREATE INDEX record_repo_rev_idx 138 - ON record("repoRev"); 139 - 140 - -- Create Blob Table 141 - CREATE TABLE IF NOT EXISTS blob ( 142 - cid character varying NOT NULL, 143 - did character varying NOT NULL, 144 - "mimeType" character varying NOT NULL, 145 - size integer NOT NULL, 146 - "tempKey" character varying, 147 - width integer, 148 - height integer, 149 - "createdAt" character varying NOT NULL, 150 - "takedownRef" character varying 151 - ); 152 - ALTER TABLE ONLY blob 153 - ADD CONSTRAINT blob_pkey PRIMARY KEY (cid, did); 154 - CREATE INDEX blob_tempkey_idx 155 - ON blob("tempKey"); 156 - 157 - -- Create Record Blob Table 158 - CREATE TABLE IF NOT EXISTS record_blob ( 159 - "blobCid" character varying NOT NULL, 160 - "recordUri" character varying NOT NULL, 161 - did character varying NOT NULL 162 - ); 163 - ALTER TABLE ONLY record_blob 164 - DROP CONSTRAINT IF EXISTS record_blob_pkey; 165 - ALTER TABLE ONLY record_blob 166 - ADD CONSTRAINT record_blob_pkey PRIMARY KEY ("blobCid","recordUri"); 167 - 168 - -- Create Backlink Table 169 - CREATE TABLE IF NOT EXISTS backlink ( 170 - uri character varying NOT NULL, 171 - path character varying NOT NULL, 172 - "linkTo" character varying NOT NULL 173 - ); 174 - ALTER TABLE ONLY backlink 175 - DROP CONSTRAINT IF EXISTS backlink_pkey; 176 - ALTER TABLE ONLY backlink 177 - ADD CONSTRAINT backlink_pkey PRIMARY KEY (uri, path); 178 - CREATE INDEX backlink_link_to_idx 179 - ON backlink(path, "linkTo"); 180 - 181 - -- Create Account Preferences Table 182 - CREATE TABLE IF NOT EXISTS account_pref ( 183 - id SERIAL PRIMARY KEY, 184 - did character varying NOT NULL, 185 - name character varying NOT NULL, 186 - "valueJson" text 187 - ); 188 - 189 - -- did-cache implementation 190 - -- Create DID Cache Table 191 - CREATE TABLE IF NOT EXISTS did_doc ( 192 - did character varying PRIMARY KEY, 193 - doc text NOT NULL, 194 - "updatedAt" bigint NOT NULL 195 - ); 196 - 197 - -- sequencer implementation 198 - -- Create Repo Sequence Table 199 - CREATE TABLE IF NOT EXISTS repo_seq ( 200 - seq bigserial PRIMARY KEY, 201 - did character varying NOT NULL, 202 - "eventType" character varying NOT NULL, 203 - event bytea NOT NULL, 204 - invalidated smallint NOT NULL DEFAULT 0, 205 - "sequencedAt" character varying NOT NULL 206 - ); 207 - CREATE INDEX repo_seq_did_idx -- for filtering seqs based on did 208 - ON repo_seq(did); 209 - CREATE INDEX repo_seq_event_type_idx -- for filtering seqs based on event type 210 - ON repo_seq("eventType"); 211 - CREATE INDEX repo_seq_sequenced_at_index -- for entering into the seq stream at a particular time 212 - ON repo_seq("sequencedAt"); 213 - 214 - -- OAuth schema 215 - CREATE TABLE IF NOT EXISTS authorization_request 216 - ( 217 - id character varying NOT NULL, 218 - did character varying, 219 - "deviceId" character varying, 220 - "clientId" character varying NOT NULL, 221 - "clientAuth" character varying NOT NULL, 222 - parameters character varying NOT NULL, 223 - "expiresAt" Timestamptz NOT NULL, 224 - code character varying 225 - ); 226 - 227 - ALTER TABLE ONLY authorization_request 228 - DROP CONSTRAINT IF EXISTS authorization_request_code_idx; 229 - ALTER TABLE ONLY authorization_request 230 - ADD CONSTRAINT authorization_request_code_idx PRIMARY KEY (id); 231 - 232 - -- TODO expires at index 233 - 234 - CREATE TABLE IF NOT EXISTS device 235 - ( 236 - id character varying NOT NULL, 237 - "sessionId" character varying unique, 238 - "userAgent" character varying, 239 - "ipAddress" character varying NOT NULL, 240 - "lastSeenAt" Timestamptz NOT NULL 241 - ); 242 - 243 - ALTER TABLE ONLY device 244 - DROP CONSTRAINT IF EXISTS pds_idx; 245 - ALTER TABLE ONLY device 246 - ADD CONSTRAINT pds_idx PRIMARY KEY (id); 247 - 248 - CREATE TABLE IF NOT EXISTS device_account 249 - ( 250 - did character varying NOT NULL, 251 - "deviceId" character varying NOT NULL, 252 - "authenticatedAt" Timestamptz NOT NULL, 253 - remember boolean NOT NULL, 254 - "authorizedClients" character varying NOT NULL 255 - ); 256 - 257 - ALTER TABLE ONLY device_account 258 - DROP CONSTRAINT IF EXISTS device_account_pk; 259 - ALTER TABLE ONLY device_account 260 - ADD CONSTRAINT device_account_pk PRIMARY KEY ("deviceId", did); 261 - 262 - --TODO add foreign key constraints 263 - 264 - CREATE TABLE IF NOT EXISTS token 265 - ( 266 - id character varying NOT NULL, --TODO 267 - did character varying NOT NULL, 268 - "tokenId" character varying NOT NULL unique, 269 - "createdAt" Timestamptz NOT NULL, 270 - "updatedAt" Timestamptz NOT NULL, 271 - "expiresAt" Timestamptz NOT NULL, 272 - "clientId" character varying NOT NULL, 273 - "clientAuth" character varying NOT NULL, 274 - "deviceId" character varying, 275 - parameters character varying NOT NULL, 276 - details character varying, 277 - code character varying, 278 - "currentRefreshToken" character varying unique 279 - ); 280 - 281 - ALTER TABLE ONLY token 282 - DROP CONSTRAINT IF EXISTS token_idx; 283 - ALTER TABLE ONLY token 284 - ADD CONSTRAINT token_idx PRIMARY KEY (id); 285 - 286 - CREATE TABLE IF NOT EXISTS used_refresh_token 287 - ( 288 - "refreshToken" character varying NOT NULL, 289 - "tokenId" character varying NOT NULL 290 - ); 291 - 292 - ALTER TABLE ONLY used_refresh_token 293 - DROP CONSTRAINT IF EXISTS used_refresh_token_pk; 294 - ALTER TABLE ONLY used_refresh_token 295 - ADD CONSTRAINT used_refresh_token_pk PRIMARY KEY ("refreshToken");
-7
migrations/20250104202448_init.down.sql
··· 1 - DROP TABLE IF EXISTS invites; 2 - 3 - DROP TABLE IF EXISTS handles; 4 - 5 - DROP TABLE IF EXISTS accounts; 6 - 7 - DROP TABLE IF EXISTS sessions;
-29
migrations/20250104202448_init.up.sql
··· 1 - CREATE TABLE IF NOT EXISTS accounts ( 2 - did TEXT PRIMARY KEY NOT NULL, 3 - email TEXT NOT NULL UNIQUE, 4 - password TEXT NOT NULL, 5 - root TEXT NOT NULL, 6 - rev TEXT NOT NULL, 7 - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 8 - ); 9 - 10 - CREATE TABLE IF NOT EXISTS handles ( 11 - handle TEXT PRIMARY KEY NOT NULL, 12 - did TEXT NOT NULL, 13 - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 14 - FOREIGN KEY (did) REFERENCES accounts(did) 15 - ); 16 - 17 - CREATE TABLE IF NOT EXISTS invites ( 18 - id TEXT PRIMARY KEY NOT NULL, 19 - did TEXT, 20 - count INTEGER NOT NULL DEFAULT 1, 21 - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 22 - ); 23 - 24 - CREATE TABLE IF NOT EXISTS sessions ( 25 - id TEXT PRIMARY KEY NOT NULL, 26 - did TEXT NOT NULL, 27 - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 28 - FOREIGN KEY (did) REFERENCES accounts(did) 29 - );
-1
migrations/20250217052304_repo_status.down.sql
··· 1 - ALTER TABLE accounts DROP COLUMN status;
-1
migrations/20250217052304_repo_status.up.sql
··· 1 - ALTER TABLE accounts ADD COLUMN status TEXT NOT NULL DEFAULT "active";
-1
migrations/20250219055555_account_plc_root.down.sql
··· 1 - ALTER TABLE accounts DROP COLUMN plc_root;
-1
migrations/20250219055555_account_plc_root.up.sql
··· 1 - ALTER TABLE accounts ADD COLUMN plc_root TEXT NOT NULL;
-1
migrations/20250220235950_private_data.down.sql
··· 1 - ALTER TABLE accounts DROP COLUMN private_prefs;
-1
migrations/20250220235950_private_data.up.sql
··· 1 - ALTER TABLE accounts ADD COLUMN private_prefs JSON;
-1
migrations/20250223015249_blob_ref.down.sql
··· 1 - DROP TABLE blob_ref;
-6
migrations/20250223015249_blob_ref.up.sql
··· 1 - CREATE TABLE IF NOT EXISTS blob_ref ( 2 - -- N.B: There is a hidden `rowid` field inserted by sqlite. 3 - cid TEXT NOT NULL, 4 - did TEXT NOT NULL, 5 - record TEXT 6 - );
-1
migrations/20250330074000_oauth.down.sql
··· 1 - DROP TABLE oauth_par_requests;
-37
migrations/20250330074000_oauth.up.sql
··· 1 - CREATE TABLE IF NOT EXISTS oauth_par_requests ( 2 - request_uri TEXT PRIMARY KEY NOT NULL, 3 - client_id TEXT NOT NULL, 4 - response_type TEXT NOT NULL, 5 - code_challenge TEXT NOT NULL, 6 - code_challenge_method TEXT NOT NULL, 7 - state TEXT, 8 - login_hint TEXT, 9 - scope TEXT, 10 - redirect_uri TEXT, 11 - response_mode TEXT, 12 - display TEXT, 13 - created_at INTEGER NOT NULL, 14 - expires_at INTEGER NOT NULL 15 - ); 16 - CREATE TABLE IF NOT EXISTS oauth_authorization_codes ( 17 - code TEXT PRIMARY KEY NOT NULL, 18 - client_id TEXT NOT NULL, 19 - subject TEXT NOT NULL, 20 - code_challenge TEXT NOT NULL, 21 - code_challenge_method TEXT NOT NULL, 22 - redirect_uri TEXT NOT NULL, 23 - scope TEXT, 24 - created_at INTEGER NOT NULL, 25 - expires_at INTEGER NOT NULL, 26 - used BOOLEAN NOT NULL DEFAULT FALSE 27 - ); 28 - CREATE TABLE IF NOT EXISTS oauth_refresh_tokens ( 29 - token TEXT PRIMARY KEY NOT NULL, 30 - client_id TEXT NOT NULL, 31 - subject TEXT NOT NULL, 32 - dpop_thumbprint TEXT NOT NULL, 33 - scope TEXT, 34 - created_at INTEGER NOT NULL, 35 - expires_at INTEGER NOT NULL, 36 - revoked BOOLEAN NOT NULL DEFAULT FALSE 37 - );
-6
migrations/20250502032700_jti.down.sql
··· 1 - DROP INDEX IF EXISTS idx_jtis_expires_at; 2 - DROP INDEX IF EXISTS idx_refresh_tokens_expires_at; 3 - DROP INDEX IF EXISTS idx_auth_codes_expires_at; 4 - DROP INDEX IF EXISTS idx_par_expires_at; 5 - 6 - DROP TABLE IF EXISTS oauth_used_jtis;
-13
migrations/20250502032700_jti.up.sql
··· 1 - -- Table for tracking used JTIs to prevent replay attacks 2 - CREATE TABLE IF NOT EXISTS oauth_used_jtis ( 3 - jti TEXT PRIMARY KEY NOT NULL, 4 - issuer TEXT NOT NULL, 5 - created_at INTEGER NOT NULL, 6 - expires_at INTEGER NOT NULL 7 - ); 8 - 9 - -- Create indexes for faster lookups and cleanup 10 - CREATE INDEX IF NOT EXISTS idx_par_expires_at ON oauth_par_requests(expires_at); 11 - CREATE INDEX IF NOT EXISTS idx_auth_codes_expires_at ON oauth_authorization_codes(expires_at); 12 - CREATE INDEX IF NOT EXISTS idx_refresh_tokens_expires_at ON oauth_refresh_tokens(expires_at); 13 - CREATE INDEX IF NOT EXISTS idx_jtis_expires_at ON oauth_used_jtis(expires_at);
-16
migrations/20250508251242_actor_store.down.sql
··· 1 - -- Drop indexes 2 - DROP INDEX IF EXISTS idx_backlink_link_to; 3 - DROP INDEX IF EXISTS idx_blob_tempkey; 4 - DROP INDEX IF EXISTS idx_record_repo_rev; 5 - DROP INDEX IF EXISTS idx_record_collection; 6 - DROP INDEX IF EXISTS idx_record_cid; 7 - DROP INDEX IF EXISTS idx_repo_block_repo_rev; 8 - 9 - -- Drop tables 10 - DROP TABLE IF EXISTS account_pref; 11 - DROP TABLE IF EXISTS backlink; 12 - DROP TABLE IF EXISTS record_blob; 13 - DROP TABLE IF EXISTS blob; 14 - DROP TABLE IF EXISTS record; 15 - DROP TABLE IF EXISTS repo_block; 16 - DROP TABLE IF EXISTS repo_root;
-70
migrations/20250508251242_actor_store.up.sql
··· 1 - -- Actor store schema matching TypeScript implementation 2 - 3 - -- Repository root information 4 - CREATE TABLE IF NOT EXISTS repo_root ( 5 - did TEXT PRIMARY KEY NOT NULL, 6 - cid TEXT NOT NULL, 7 - rev TEXT NOT NULL, 8 - indexedAt TEXT NOT NULL 9 - ); 10 - 11 - -- Repository blocks (IPLD blocks) 12 - CREATE TABLE IF NOT EXISTS repo_block ( 13 - cid TEXT PRIMARY KEY NOT NULL, 14 - repoRev TEXT NOT NULL, 15 - size INTEGER NOT NULL, 16 - content BLOB NOT NULL 17 - ); 18 - 19 - -- Record index 20 - CREATE TABLE IF NOT EXISTS record ( 21 - uri TEXT PRIMARY KEY NOT NULL, 22 - cid TEXT NOT NULL, 23 - collection TEXT NOT NULL, 24 - rkey TEXT NOT NULL, 25 - repoRev TEXT NOT NULL, 26 - indexedAt TEXT NOT NULL, 27 - takedownRef TEXT 28 - ); 29 - 30 - -- Blob storage metadata 31 - CREATE TABLE IF NOT EXISTS blob ( 32 - cid TEXT PRIMARY KEY NOT NULL, 33 - mimeType TEXT NOT NULL, 34 - size INTEGER NOT NULL, 35 - tempKey TEXT, 36 - width INTEGER, 37 - height INTEGER, 38 - createdAt TEXT NOT NULL, 39 - takedownRef TEXT 40 - ); 41 - 42 - -- Record-blob associations 43 - CREATE TABLE IF NOT EXISTS record_blob ( 44 - blobCid TEXT NOT NULL, 45 - recordUri TEXT NOT NULL, 46 - PRIMARY KEY (blobCid, recordUri) 47 - ); 48 - 49 - -- Backlinks between records 50 - CREATE TABLE IF NOT EXISTS backlink ( 51 - uri TEXT NOT NULL, 52 - path TEXT NOT NULL, 53 - linkTo TEXT NOT NULL, 54 - PRIMARY KEY (uri, path) 55 - ); 56 - 57 - -- User preferences 58 - CREATE TABLE IF NOT EXISTS account_pref ( 59 - id INTEGER PRIMARY KEY AUTOINCREMENT, 60 - name TEXT NOT NULL, 61 - valueJson TEXT NOT NULL 62 - ); 63 - 64 - -- Create indexes 65 - CREATE INDEX IF NOT EXISTS idx_repo_block_repo_rev ON repo_block(repoRev, cid); 66 - CREATE INDEX IF NOT EXISTS idx_record_cid ON record(cid); 67 - CREATE INDEX IF NOT EXISTS idx_record_collection ON record(collection); 68 - CREATE INDEX IF NOT EXISTS idx_record_repo_rev ON record(repoRev); 69 - CREATE INDEX IF NOT EXISTS idx_blob_tempkey ON blob(tempKey); 70 - CREATE INDEX IF NOT EXISTS idx_backlink_link_to ON backlink(path, linkTo);
-15
migrations/20250508252057_blockstore.up.sql
··· 1 - CREATE TABLE IF NOT EXISTS blocks ( 2 - cid TEXT PRIMARY KEY NOT NULL, 3 - data BLOB NOT NULL, 4 - multicodec INTEGER NOT NULL, 5 - multihash INTEGER NOT NULL 6 - ); 7 - CREATE TABLE IF NOT EXISTS tree_nodes ( 8 - repo_did TEXT NOT NULL, 9 - key TEXT NOT NULL, 10 - value_cid TEXT NOT NULL, 11 - PRIMARY KEY (repo_did, key), 12 - FOREIGN KEY (value_cid) REFERENCES blocks(cid) 13 - ); 14 - CREATE INDEX IF NOT EXISTS idx_blocks_cid ON blocks(cid); 15 - CREATE INDEX IF NOT EXISTS idx_tree_nodes_repo ON tree_nodes(repo_did);
-5
migrations/20250510222500_actor_migration.up.sql
··· 1 - CREATE TABLE IF NOT EXISTS actor_migration ( 2 - id INTEGER PRIMARY KEY AUTOINCREMENT, 3 - name TEXT NOT NULL, 4 - appliedAt TEXT NOT NULL 5 - );
+8 -3
src/account_manager/helpers/account.rs
··· 3 3 //! 4 4 //! Modified for SQLite backend 5 5 use crate::schema::pds::account::dsl as AccountSchema; 6 + use crate::schema::pds::account::table as AccountTable; 6 7 use crate::schema::pds::actor::dsl as ActorSchema; 8 + use crate::schema::pds::actor::table as ActorTable; 7 9 use anyhow::Result; 8 10 use chrono::DateTime; 9 11 use chrono::offset::Utc as UtcOffset; 10 - use diesel::dsl::{exists, not}; 11 12 use diesel::result::{DatabaseErrorKind, Error as DieselError}; 12 13 use diesel::*; 13 14 use rsky_common::RFC3339_VARIANT; 14 15 use rsky_lexicon::com::atproto::admin::StatusAttr; 15 16 #[expect(unused_imports)] 16 17 pub(crate) use rsky_pds::account_manager::helpers::account::{ 17 - AccountStatus, ActorAccount, ActorJoinAccount, AvailabilityFlags, FormattedAccountStatus, 18 + AccountStatus, ActorAccount, AvailabilityFlags, FormattedAccountStatus, 18 19 GetAccountAdminStatusOutput, format_account_status, 19 20 }; 20 21 use std::ops::Add; 21 22 use std::time::SystemTime; 22 23 use thiserror::Error; 23 24 25 + use diesel::dsl::{LeftJoinOn, exists, not}; 26 + use diesel::helper_types::{Eq, IntoBoxed}; 27 + 24 28 #[derive(Error, Debug)] 25 29 pub enum AccountHelperError { 26 30 #[error("UserAlreadyExistsError")] ··· 28 32 #[error("DatabaseError: `{0}`")] 29 33 DieselError(String), 30 34 } 31 - 35 + pub type ActorJoinAccount = 36 + LeftJoinOn<ActorTable, AccountTable, Eq<ActorSchema::did, AccountSchema::did>>; 32 37 pub type BoxedQuery<'life> = dsl::IntoBoxed<'life, ActorJoinAccount, sqlite::Sqlite>; 33 38 pub fn select_account_qb(flags: Option<AvailabilityFlags>) -> BoxedQuery<'static> { 34 39 let AvailabilityFlags {
+1 -1
src/account_manager/helpers/auth.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 + use crate::models::pds as models; 5 6 use anyhow::Result; 6 7 use diesel::*; 7 8 use rsky_common::time::from_micros_to_utc; ··· 12 13 RefreshToken, ServiceJwtHeader, ServiceJwtParams, ServiceJwtPayload, create_access_token, 13 14 create_refresh_token, create_service_jwt, create_tokens, decode_refresh_token, 14 15 }; 15 - use rsky_pds::models; 16 16 17 17 pub async fn store_refresh_token( 18 18 payload: RefreshToken,
+2 -80
src/account_manager/helpers/email_token.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 - #![allow(unnameable_types, unused_qualifications)] 5 + use crate::models::pds::EmailToken; 6 + use crate::models::pds::EmailTokenPurpose; 6 7 use anyhow::{Result, bail}; 7 8 use diesel::*; 8 9 use rsky_common::time::{MINUTE, from_str_to_utc, less_than_ago_s}; 9 10 use rsky_pds::apis::com::atproto::server::get_random_token; 10 - use rsky_pds::models::EmailToken; 11 11 12 12 pub async fn create_email_token( 13 13 did: &str, ··· 121 121 Ok(res.did) 122 122 } else { 123 123 bail!("Token is invalid") 124 - } 125 - } 126 - 127 - #[derive( 128 - Clone, 129 - Copy, 130 - Debug, 131 - PartialEq, 132 - Eq, 133 - Hash, 134 - Default, 135 - serde::Serialize, 136 - serde::Deserialize, 137 - AsExpression, 138 - )] 139 - #[diesel(sql_type = sql_types::Text)] 140 - pub enum EmailTokenPurpose { 141 - #[default] 142 - ConfirmEmail, 143 - UpdateEmail, 144 - ResetPassword, 145 - DeleteAccount, 146 - PlcOperation, 147 - } 148 - 149 - impl EmailTokenPurpose { 150 - pub const fn as_str(&self) -> &'static str { 151 - match self { 152 - Self::ConfirmEmail => "confirm_email", 153 - Self::UpdateEmail => "update_email", 154 - Self::ResetPassword => "reset_password", 155 - Self::DeleteAccount => "delete_account", 156 - Self::PlcOperation => "plc_operation", 157 - } 158 - } 159 - 160 - pub fn from_str(s: &str) -> Result<Self> { 161 - match s { 162 - "confirm_email" => Ok(Self::ConfirmEmail), 163 - "update_email" => Ok(Self::UpdateEmail), 164 - "reset_password" => Ok(Self::ResetPassword), 165 - "delete_account" => Ok(Self::DeleteAccount), 166 - "plc_operation" => Ok(Self::PlcOperation), 167 - _ => bail!("Unable to parse as EmailTokenPurpose: `{s:?}`"), 168 - } 169 - } 170 - } 171 - 172 - impl<DB> Queryable<sql_types::Text, DB> for EmailTokenPurpose 173 - where 174 - DB: backend::Backend, 175 - String: deserialize::FromSql<sql_types::Text, DB>, 176 - { 177 - type Row = String; 178 - 179 - fn build(s: String) -> deserialize::Result<Self> { 180 - Ok(Self::from_str(&s)?) 181 - } 182 - } 183 - 184 - impl serialize::ToSql<sql_types::Text, sqlite::Sqlite> for EmailTokenPurpose 185 - where 186 - String: serialize::ToSql<sql_types::Text, sqlite::Sqlite>, 187 - { 188 - fn to_sql<'lifetime>( 189 - &'lifetime self, 190 - out: &mut serialize::Output<'lifetime, '_, sqlite::Sqlite>, 191 - ) -> serialize::Result { 192 - serialize::ToSql::<sql_types::Text, sqlite::Sqlite>::to_sql( 193 - match self { 194 - Self::ConfirmEmail => "confirm_email", 195 - Self::UpdateEmail => "update_email", 196 - Self::ResetPassword => "reset_password", 197 - Self::DeleteAccount => "delete_account", 198 - Self::PlcOperation => "plc_operation", 199 - }, 200 - out, 201 - ) 202 124 } 203 125 } 204 126
+1 -1
src/account_manager/helpers/invite.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 + use crate::models::pds as models; 5 6 use anyhow::{Result, bail}; 6 7 use diesel::*; 7 8 use rsky_lexicon::com::atproto::server::AccountCodes; ··· 9 10 InviteCode as LexiconInviteCode, InviteCodeUse as LexiconInviteCodeUse, 10 11 }; 11 12 use rsky_pds::account_manager::DisableInviteCodesOpts; 12 - use rsky_pds::models::models; 13 13 use std::collections::BTreeMap; 14 14 use std::mem; 15 15
+2 -2
src/account_manager/helpers/password.rs
··· 2 2 //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 3 3 //! 4 4 //! Modified for SQLite backend 5 + use crate::models::pds as models; 6 + use crate::models::pds::AppPassword; 5 7 use anyhow::{Result, bail}; 6 8 use diesel::*; 7 9 use rsky_common::{get_random_str, now}; ··· 10 12 pub(crate) use rsky_pds::account_manager::helpers::password::{ 11 13 UpdateUserPasswordOpts, gen_salt_and_hash, hash_app_password, hash_with_salt, verify, 12 14 }; 13 - use rsky_pds::models; 14 - use rsky_pds::models::AppPassword; 15 15 16 16 pub async fn verify_account_password( 17 17 did: &str,
+1 -1
src/account_manager/mod.rs
··· 10 10 }; 11 11 use crate::account_manager::helpers::invite::CodeDetail; 12 12 use crate::account_manager::helpers::password::UpdateUserPasswordOpts; 13 + use crate::models::pds::EmailTokenPurpose; 13 14 use anyhow::Result; 14 15 use chrono::DateTime; 15 16 use chrono::offset::Utc as UtcOffset; 16 17 use cidv10::Cid; 17 18 use diesel::*; 18 19 use futures::try_join; 19 - use helpers::email_token::EmailTokenPurpose; 20 20 use helpers::{account, auth, email_token, invite, password, repo}; 21 21 use rsky_common::RFC3339_VARIANT; 22 22 use rsky_common::time::{HOUR, from_micros_to_str, from_str_to_micros};
+32 -3
src/actor_store/blob.rs
··· 4 4 //! 5 5 //! Modified for SQLite backend 6 6 7 + use crate::models::pds as models; 7 8 use anyhow::{Result, bail}; 8 9 use cidv10::Cid; 9 10 use diesel::dsl::{count_distinct, exists, not}; ··· 19 20 use rsky_lexicon::com::atproto::admin::StatusAttr; 20 21 use rsky_lexicon::com::atproto::repo::ListMissingBlobsRefRecordBlob; 21 22 use rsky_pds::actor_store::blob::{ 22 - BlobMetadata, GetBlobMetadataOutput, ListBlobsOpts, ListMissingBlobsOpts, sha256_stream, 23 - verify_blob, 23 + BlobMetadata, GetBlobMetadataOutput, ListBlobsOpts, ListMissingBlobsOpts, accepted_mime, 24 + sha256_stream, 24 25 }; 25 26 use rsky_pds::image; 26 - use rsky_pds::models::models; 27 27 use rsky_repo::error::BlobError; 28 28 use rsky_repo::types::{PreparedBlobRef, PreparedWrite}; 29 29 use std::str::FromStr as _; ··· 692 692 } 693 693 } 694 694 } 695 + 696 + pub async fn verify_blob(blob: &PreparedBlobRef, found: &models::Blob) -> Result<()> { 697 + if let Some(max_size) = blob.constraints.max_size { 698 + if found.size as usize > max_size { 699 + bail!( 700 + "BlobTooLarge: This file is too large. It is {:?} but the maximum size is {:?}", 701 + found.size, 702 + max_size 703 + ) 704 + } 705 + } 706 + if blob.mime_type != found.mime_type { 707 + bail!( 708 + "InvalidMimeType: Referenced MimeType does not match stored blob. Expected: {:?}, Got: {:?}", 709 + found.mime_type, 710 + blob.mime_type 711 + ) 712 + } 713 + if let Some(ref accept) = blob.constraints.accept { 714 + if !accepted_mime(blob.mime_type.clone(), accept.clone()).await { 715 + bail!( 716 + "Wrong type of file. It is {:?} but it must match {:?}.", 717 + blob.mime_type, 718 + accept 719 + ) 720 + } 721 + } 722 + Ok(()) 723 + }
+1 -1
src/actor_store/preference.rs
··· 4 4 //! 5 5 //! Modified for SQLite backend 6 6 7 + use crate::models::pds::AccountPref; 7 8 use anyhow::{Result, bail}; 8 9 use diesel::*; 9 10 use rsky_lexicon::app::bsky::actor::RefPreferences; 10 11 use rsky_pds::actor_store::preference::pref_match_namespace; 11 12 use rsky_pds::actor_store::preference::util::pref_in_scope; 12 13 use rsky_pds::auth_verifier::AuthScope; 13 - use rsky_pds::models::AccountPref; 14 14 15 15 pub struct PreferenceReader { 16 16 pub did: String,
+50 -3
src/actor_store/record.rs
··· 4 4 //! 5 5 //! Modified for SQLite backend 6 6 7 + use crate::models::pds::{Backlink, Record, RepoBlock}; 7 8 use anyhow::{Result, bail}; 8 9 use cidv10::Cid; 9 10 use diesel::result::Error; 10 11 use diesel::*; 11 12 use futures::stream::{self, StreamExt}; 12 13 use rsky_lexicon::com::atproto::admin::StatusAttr; 13 - use rsky_pds::actor_store::record::{GetRecord, RecordsForCollection, get_backlinks}; 14 - use rsky_pds::models::{Backlink, Record, RepoBlock}; 15 - use rsky_repo::types::{RepoRecord, WriteOpAction}; 14 + use rsky_pds::actor_store::record::{GetRecord, RecordsForCollection}; 15 + use rsky_repo::storage::Ipld; 16 + use rsky_repo::types::{Ids, Lex, RepoRecord, WriteOpAction}; 16 17 use rsky_repo::util::cbor_to_lex_record; 17 18 use rsky_syntax::aturi::AtUri; 19 + use rsky_syntax::aturi_validation::ensure_valid_at_uri; 20 + use rsky_syntax::did::ensure_valid_did; 21 + use serde_json::Value as JsonValue; 18 22 use std::env; 19 23 use std::str::FromStr; 24 + 25 + // @NOTE in the future this can be replaced with a more generic routine that pulls backlinks based on lex docs. 26 + // For now, we just want to ensure we're tracking links from follows, blocks, likes, and reposts. 27 + pub fn get_backlinks(uri: &AtUri, record: &RepoRecord) -> Result<Vec<Backlink>> { 28 + if let Some(Lex::Ipld(Ipld::Json(JsonValue::String(record_type)))) = record.get("$type") { 29 + if record_type == Ids::AppBskyGraphFollow.as_str() 30 + || record_type == Ids::AppBskyGraphBlock.as_str() 31 + { 32 + if let Some(Lex::Ipld(Ipld::Json(JsonValue::String(subject)))) = record.get("subject") { 33 + match ensure_valid_did(uri) { 34 + Ok(_) => { 35 + return Ok(vec![Backlink { 36 + uri: uri.to_string(), 37 + path: "subject".to_owned(), 38 + link_to: subject.clone(), 39 + }]); 40 + } 41 + Err(e) => bail!("get_backlinks Error: invalid did {}", e), 42 + }; 43 + } 44 + } else if record_type == Ids::AppBskyFeedLike.as_str() 45 + || record_type == Ids::AppBskyFeedRepost.as_str() 46 + { 47 + if let Some(Lex::Map(ref_object)) = record.get("subject") { 48 + if let Some(Lex::Ipld(Ipld::Json(JsonValue::String(subject_uri)))) = 49 + ref_object.get("uri") 50 + { 51 + match ensure_valid_at_uri(uri) { 52 + Ok(_) => { 53 + return Ok(vec![Backlink { 54 + uri: uri.to_string(), 55 + path: "subject.uri".to_owned(), 56 + link_to: subject_uri.clone(), 57 + }]); 58 + } 59 + Err(e) => bail!("get_backlinks Error: invalid AtUri {}", e), 60 + }; 61 + } 62 + } 63 + } 64 + } 65 + Ok(Vec::new()) 66 + } 20 67 21 68 /// Combined handler for record operations with both read and write capabilities. 22 69 pub(crate) struct RecordReader {
+2 -2
src/actor_store/sql_repo.rs
··· 3 3 //! 4 4 //! Modified for SQLite backend 5 5 6 + use crate::models::pds as models; 7 + use crate::models::pds::RepoBlock; 6 8 use anyhow::Result; 7 9 use cidv10::Cid; 8 10 use diesel::dsl::sql; ··· 10 12 use diesel::sql_types::{Bool, Text}; 11 13 use diesel::*; 12 14 use futures::{StreamExt, TryStreamExt, stream}; 13 - use rsky_pds::models; 14 - use rsky_pds::models::RepoBlock; 15 15 use rsky_repo::block_map::{BlockMap, BlocksAndMissing}; 16 16 use rsky_repo::car::blocks_to_car_file; 17 17 use rsky_repo::cid_set::CidSet;
+1
src/lib.rs
··· 11 11 mod firehose; 12 12 mod metrics; 13 13 mod mmap; 14 + mod models; 14 15 mod oauth; 15 16 mod plc; 16 17 mod schema;
+303 -111
src/models.rs
··· 5 5 6 6 pub mod pds { 7 7 8 + #![allow(unnameable_types, unused_qualifications)] 8 9 use anyhow::{Result, bail}; 9 10 use chrono::DateTime; 10 11 use chrono::offset::Utc; ··· 155 156 #[derive( 156 157 Queryable, 157 158 Identifiable, 159 + Insertable, 158 160 Selectable, 159 161 Clone, 160 162 Debug, ··· 167 169 #[diesel(check_for_backend(Sqlite))] 168 170 pub struct AccountPref { 169 171 pub id: i32, 170 - pub did: String, 171 172 pub name: String, 172 - pub valueJson: Option<String>, 173 + #[diesel(column_name = valueJson)] 174 + #[serde(rename = "valueJson")] 175 + pub value_json: Option<String>, 173 176 } 174 177 175 178 #[derive( ··· 189 192 pub struct Actor { 190 193 pub did: String, 191 194 pub handle: Option<String>, 192 - pub createdAt: String, 193 - pub takedownRef: Option<String>, 194 - pub deactivatedAt: Option<String>, 195 - pub deleteAfter: Option<String>, 195 + #[diesel(column_name = createdAt)] 196 + #[serde(rename = "createdAt")] 197 + pub created_at: String, 198 + #[diesel(column_name = takedownRef)] 199 + #[serde(rename = "takedownRef")] 200 + pub takedown_ref: Option<String>, 201 + #[diesel(column_name = deactivatedAt)] 202 + #[serde(rename = "deactivatedAt")] 203 + pub deactivated_at: Option<String>, 204 + #[diesel(column_name = deleteAfter)] 205 + #[serde(rename = "deleteAfter")] 206 + pub delete_after: Option<String>, 196 207 } 197 208 198 209 #[derive( ··· 213 224 pub did: String, 214 225 pub name: String, 215 226 pub password: String, 216 - pub createdAt: String, 227 + #[diesel(column_name = createdAt)] 228 + #[serde(rename = "createdAt")] 229 + pub created_at: String, 217 230 } 218 231 219 232 #[derive( 220 233 Queryable, 221 234 Identifiable, 222 - Selectable, 223 - Clone, 224 - Debug, 225 - PartialEq, 226 - Default, 227 - Serialize, 228 - Deserialize, 229 - )] 230 - #[diesel(table_name = crate::schema::pds::authorization_request)] 231 - #[diesel(check_for_backend(Sqlite))] 232 - pub struct AuthorizationRequest { 233 - pub id: String, 234 - pub did: Option<String>, 235 - pub deviceId: Option<String>, 236 - pub clientId: String, 237 - pub clientAuth: String, 238 - pub parameters: String, 239 - pub expiresAt: DateTime<Utc>, 240 - pub code: Option<String>, 241 - } 242 - 243 - #[derive( 244 - Queryable, 245 - Identifiable, 235 + Insertable, 246 236 Selectable, 247 237 Clone, 248 238 Debug, ··· 257 247 pub struct Backlink { 258 248 pub uri: String, 259 249 pub path: String, 260 - pub linkTo: String, 250 + #[diesel(column_name = linkTo)] 251 + #[serde(rename = "linkTo")] 252 + pub link_to: String, 261 253 } 262 254 263 255 #[derive( ··· 271 263 Serialize, 272 264 Deserialize, 273 265 )] 274 - #[diesel(primary_key(cid, did))] 266 + #[diesel(treat_none_as_null = true)] 267 + #[diesel(primary_key(cid))] 275 268 #[diesel(table_name = crate::schema::pds::blob)] 276 269 #[diesel(check_for_backend(Sqlite))] 277 270 pub struct Blob { 278 271 pub cid: String, 279 272 pub did: String, 280 - pub mimeType: String, 273 + #[diesel(column_name = mimeType)] 274 + #[serde(rename = "mimeType")] 275 + pub mime_type: String, 281 276 pub size: i32, 282 - pub tempKey: Option<String>, 277 + #[diesel(column_name = tempKey)] 278 + #[serde(rename = "tempKey")] 279 + pub temp_key: Option<String>, 283 280 pub width: Option<i32>, 284 281 pub height: Option<i32>, 285 - pub createdAt: String, 286 - pub takedownRef: Option<String>, 282 + #[diesel(column_name = createdAt)] 283 + #[serde(rename = "createdAt")] 284 + pub created_at: String, 285 + #[diesel(column_name = takedownRef)] 286 + #[serde(rename = "takedownRef")] 287 + pub takedown_ref: Option<String>, 287 288 } 288 289 289 290 #[derive( ··· 297 298 Serialize, 298 299 Deserialize, 299 300 )] 300 - #[diesel(table_name = crate::schema::pds::device)] 301 + #[diesel(primary_key(did))] 302 + #[diesel(table_name = crate::schema::pds::did_doc)] 301 303 #[diesel(check_for_backend(Sqlite))] 302 - pub struct Device { 303 - pub id: String, 304 - pub sessionId: Option<String>, 305 - pub userAgent: Option<String>, 306 - pub ipAddress: String, 307 - pub lastSeenAt: DateTime<Utc>, 304 + pub struct DidDoc { 305 + pub did: String, 306 + pub doc: String, 307 + #[diesel(column_name = updatedAt)] 308 + #[serde(rename = "updatedAt")] 309 + pub updated_at: i64, 308 310 } 309 311 310 312 #[derive( 311 - Queryable, 312 - Identifiable, 313 - Selectable, 314 - Clone, 315 - Debug, 316 - PartialEq, 317 - Default, 318 - Serialize, 319 - Deserialize, 313 + Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize, AsExpression, 320 314 )] 321 - #[diesel(primary_key(deviceId, did))] 322 - #[diesel(table_name = crate::schema::pds::device_account)] 323 - #[diesel(check_for_backend(Sqlite))] 324 - pub struct DeviceAccount { 325 - pub did: String, 326 - pub deviceId: String, 327 - pub authenticatedAt: DateTime<Utc>, 328 - pub remember: bool, 329 - pub authorizedClients: String, 315 + #[diesel(sql_type = Text)] 316 + pub enum EmailTokenPurpose { 317 + #[default] 318 + ConfirmEmail, 319 + UpdateEmail, 320 + ResetPassword, 321 + DeleteAccount, 322 + PlcOperation, 330 323 } 331 324 332 - #[derive( 333 - Queryable, 334 - Identifiable, 335 - Selectable, 336 - Clone, 337 - Debug, 338 - PartialEq, 339 - Default, 340 - Serialize, 341 - Deserialize, 342 - )] 343 - #[diesel(primary_key(did))] 344 - #[diesel(table_name = crate::schema::pds::did_doc)] 345 - #[diesel(check_for_backend(Sqlite))] 346 - pub struct DidDoc { 347 - pub did: String, 348 - pub doc: String, 349 - pub updatedAt: i64, 325 + impl EmailTokenPurpose { 326 + pub fn as_str(&self) -> &'static str { 327 + match self { 328 + EmailTokenPurpose::ConfirmEmail => "confirm_email", 329 + EmailTokenPurpose::UpdateEmail => "update_email", 330 + EmailTokenPurpose::ResetPassword => "reset_password", 331 + EmailTokenPurpose::DeleteAccount => "delete_account", 332 + EmailTokenPurpose::PlcOperation => "plc_operation", 333 + } 334 + } 335 + 336 + pub fn from_str(s: &str) -> Result<Self> { 337 + match s { 338 + "confirm_email" => Ok(EmailTokenPurpose::ConfirmEmail), 339 + "update_email" => Ok(EmailTokenPurpose::UpdateEmail), 340 + "reset_password" => Ok(EmailTokenPurpose::ResetPassword), 341 + "delete_account" => Ok(EmailTokenPurpose::DeleteAccount), 342 + "plc_operation" => Ok(EmailTokenPurpose::PlcOperation), 343 + _ => bail!("Unable to parse as EmailTokenPurpose: `{s:?}`"), 344 + } 345 + } 346 + } 347 + 348 + impl<DB> Queryable<sql_types::Text, DB> for EmailTokenPurpose 349 + where 350 + DB: backend::Backend, 351 + String: deserialize::FromSql<sql_types::Text, DB>, 352 + { 353 + type Row = String; 354 + 355 + fn build(s: String) -> deserialize::Result<Self> { 356 + Ok(Self::from_str(&s)?) 357 + } 358 + } 359 + 360 + impl serialize::ToSql<sql_types::Text, sqlite::Sqlite> for EmailTokenPurpose 361 + where 362 + String: serialize::ToSql<sql_types::Text, sqlite::Sqlite>, 363 + { 364 + fn to_sql<'lifetime>( 365 + &'lifetime self, 366 + out: &mut serialize::Output<'lifetime, '_, sqlite::Sqlite>, 367 + ) -> serialize::Result { 368 + serialize::ToSql::<sql_types::Text, sqlite::Sqlite>::to_sql( 369 + match self { 370 + Self::ConfirmEmail => "confirm_email", 371 + Self::UpdateEmail => "update_email", 372 + Self::ResetPassword => "reset_password", 373 + Self::DeleteAccount => "delete_account", 374 + Self::PlcOperation => "plc_operation", 375 + }, 376 + out, 377 + ) 378 + } 350 379 } 351 380 352 381 #[derive( ··· 364 393 #[diesel(table_name = crate::schema::pds::email_token)] 365 394 #[diesel(check_for_backend(Sqlite))] 366 395 pub struct EmailToken { 367 - pub purpose: String, 396 + pub purpose: EmailTokenPurpose, 368 397 pub did: String, 369 398 pub token: String, 370 - pub requestedAt: String, 399 + #[diesel(column_name = requestedAt)] 400 + #[serde(rename = "requestedAt")] 401 + pub requested_at: String, 371 402 } 372 403 373 404 #[derive( 374 405 Queryable, 375 406 Identifiable, 407 + Insertable, 376 408 Selectable, 377 409 Clone, 378 410 Debug, ··· 386 418 #[diesel(check_for_backend(Sqlite))] 387 419 pub struct InviteCode { 388 420 pub code: String, 389 - pub availableUses: i32, 421 + #[diesel(column_name = availableUses)] 422 + #[serde(rename = "availableUses")] 423 + pub available_uses: i32, 390 424 pub disabled: i16, 391 - pub forAccount: String, 392 - pub createdBy: String, 393 - pub createdAt: String, 425 + #[diesel(column_name = forAccount)] 426 + #[serde(rename = "forAccount")] 427 + pub for_account: String, 428 + #[diesel(column_name = createdBy)] 429 + #[serde(rename = "createdBy")] 430 + pub created_by: String, 431 + #[diesel(column_name = createdAt)] 432 + #[serde(rename = "createdAt")] 433 + pub created_at: String, 394 434 } 395 435 396 436 #[derive( ··· 409 449 #[diesel(check_for_backend(Sqlite))] 410 450 pub struct InviteCodeUse { 411 451 pub code: String, 412 - pub usedBy: String, 413 - pub usedAt: String, 452 + #[diesel(column_name = usedBy)] 453 + #[serde(rename = "usedBy")] 454 + pub used_by: String, 455 + #[diesel(column_name = usedAt)] 456 + #[serde(rename = "usedAt")] 457 + pub used_at: String, 414 458 } 415 459 416 460 #[derive( 417 461 Queryable, 418 462 Identifiable, 463 + Insertable, 419 464 Selectable, 420 465 Clone, 421 466 Debug, ··· 433 478 pub did: String, 434 479 pub collection: String, 435 480 pub rkey: String, 436 - pub repoRev: Option<String>, 437 - pub indexedAt: String, 438 - pub takedownRef: Option<String>, 481 + #[diesel(column_name = repoRev)] 482 + #[serde(rename = "repoRev")] 483 + pub repo_rev: Option<String>, 484 + #[diesel(column_name = indexedAt)] 485 + #[serde(rename = "indexedAt")] 486 + pub indexed_at: String, 487 + #[diesel(column_name = takedownRef)] 488 + #[serde(rename = "takedownRef")] 489 + pub takedown_ref: Option<String>, 439 490 } 440 491 441 492 #[derive( 493 + QueryableByName, 442 494 Queryable, 443 495 Identifiable, 444 496 Selectable, ··· 453 505 #[diesel(table_name = crate::schema::pds::record_blob)] 454 506 #[diesel(check_for_backend(Sqlite))] 455 507 pub struct RecordBlob { 456 - pub blobCid: String, 457 - pub recordUri: String, 508 + #[diesel(column_name = blobCid, sql_type = Text)] 509 + #[serde(rename = "blobCid")] 510 + pub blob_cid: String, 511 + #[diesel(column_name = recordUri, sql_type = Text)] 512 + #[serde(rename = "recordUri")] 513 + pub record_uri: String, 514 + #[diesel(sql_type = Text)] 458 515 pub did: String, 459 516 } 460 517 ··· 489 546 Queryable, 490 547 Identifiable, 491 548 Selectable, 549 + Insertable, 492 550 Clone, 493 551 Debug, 494 552 PartialEq, ··· 496 554 Serialize, 497 555 Deserialize, 498 556 )] 499 - #[diesel(primary_key(cid, did))] 557 + #[diesel(primary_key(cid))] 500 558 #[diesel(table_name = crate::schema::pds::repo_block)] 501 559 #[diesel(check_for_backend(Sqlite))] 502 560 pub struct RepoBlock { 561 + #[diesel(sql_type = Text)] 503 562 pub cid: String, 504 563 pub did: String, 505 - pub repoRev: String, 564 + #[diesel(column_name = repoRev)] 565 + #[serde(rename = "repoRev")] 566 + pub repo_rev: String, 506 567 pub size: i32, 568 + #[diesel(sql_type = Bytea)] 507 569 pub content: Vec<u8>, 508 570 } 509 571 ··· 525 587 pub did: String, 526 588 pub cid: String, 527 589 pub rev: String, 528 - pub indexedAt: String, 590 + #[diesel(column_name = indexedAt)] 591 + #[serde(rename = "indexedAt")] 592 + pub indexed_at: String, 529 593 } 530 594 531 595 #[derive( 532 596 Queryable, 533 597 Identifiable, 534 598 Selectable, 599 + Insertable, 535 600 Clone, 536 601 Debug, 537 602 PartialEq, ··· 543 608 #[diesel(table_name = crate::schema::pds::repo_seq)] 544 609 #[diesel(check_for_backend(Sqlite))] 545 610 pub struct RepoSeq { 546 - pub seq: i64, 611 + #[diesel(deserialize_as = i64)] 612 + pub seq: Option<i64>, 547 613 pub did: String, 548 - pub eventType: String, 614 + #[diesel(column_name = eventType)] 615 + #[serde(rename = "eventType")] 616 + pub event_type: String, 617 + #[diesel(sql_type = Bytea)] 549 618 pub event: Vec<u8>, 550 - pub invalidated: i16, 551 - pub sequencedAt: String, 619 + #[diesel(deserialize_as = i16)] 620 + pub invalidated: Option<i16>, 621 + #[diesel(column_name = sequencedAt)] 622 + #[serde(rename = "sequencedAt")] 623 + pub sequenced_at: String, 624 + } 625 + 626 + impl RepoSeq { 627 + pub fn new(did: String, event_type: String, event: Vec<u8>, sequenced_at: String) -> Self { 628 + RepoSeq { 629 + did, 630 + event_type, 631 + event, 632 + sequenced_at, 633 + invalidated: None, // default values used on insert 634 + seq: None, // default values used on insert 635 + } 636 + } 552 637 } 553 638 554 639 #[derive( 555 640 Queryable, 556 641 Identifiable, 642 + Insertable, 557 643 Selectable, 558 644 Clone, 559 645 Debug, ··· 562 648 Serialize, 563 649 Deserialize, 564 650 )] 651 + #[diesel(primary_key(id))] 565 652 #[diesel(table_name = crate::schema::pds::token)] 566 653 #[diesel(check_for_backend(Sqlite))] 567 654 pub struct Token { 568 655 pub id: String, 569 656 pub did: String, 570 - pub tokenId: String, 571 - pub createdAt: DateTime<Utc>, 572 - pub updatedAt: DateTime<Utc>, 573 - pub expiresAt: DateTime<Utc>, 574 - pub clientId: String, 575 - pub clientAuth: String, 576 - pub deviceId: Option<String>, 657 + #[diesel(column_name = tokenId)] 658 + #[serde(rename = "tokenId")] 659 + pub token_id: String, 660 + #[diesel(column_name = createdAt)] 661 + #[serde(rename = "createdAt")] 662 + pub created_at: DateTime<Utc>, 663 + #[diesel(column_name = updatedAt)] 664 + #[serde(rename = "updatedAt")] 665 + pub updated_at: DateTime<Utc>, 666 + #[diesel(column_name = expiresAt)] 667 + #[serde(rename = "expiresAt")] 668 + pub expires_at: DateTime<Utc>, 669 + #[diesel(column_name = clientId)] 670 + #[serde(rename = "clientId")] 671 + pub client_id: String, 672 + #[diesel(column_name = clientAuth)] 673 + #[serde(rename = "clientAuth")] 674 + pub client_auth: String, 675 + #[diesel(column_name = deviceId)] 676 + #[serde(rename = "deviceId")] 677 + pub device_id: Option<String>, 577 678 pub parameters: String, 578 679 pub details: Option<String>, 579 680 pub code: Option<String>, 580 - pub currentRefreshToken: Option<String>, 681 + #[diesel(column_name = currentRefreshToken)] 682 + #[serde(rename = "currentRefreshToken")] 683 + pub current_refresh_token: Option<String>, 684 + } 685 + 686 + #[derive( 687 + Queryable, 688 + Identifiable, 689 + Insertable, 690 + Selectable, 691 + Clone, 692 + Debug, 693 + PartialEq, 694 + Default, 695 + Serialize, 696 + Deserialize, 697 + )] 698 + #[diesel(primary_key(id))] 699 + #[diesel(table_name = crate::schema::pds::device)] 700 + #[diesel(check_for_backend(Sqlite))] 701 + pub struct Device { 702 + pub id: String, 703 + #[diesel(column_name = sessionId)] 704 + #[serde(rename = "sessionId")] 705 + pub session_id: Option<String>, 706 + #[diesel(column_name = userAgent)] 707 + #[serde(rename = "userAgent")] 708 + pub user_agent: Option<String>, 709 + #[diesel(column_name = ipAddress)] 710 + #[serde(rename = "ipAddress")] 711 + pub ip_address: String, 712 + #[diesel(column_name = lastSeenAt)] 713 + #[serde(rename = "lastSeenAt")] 714 + pub last_seen_at: DateTime<Utc>, 715 + } 716 + 717 + #[derive( 718 + Queryable, 719 + Identifiable, 720 + Insertable, 721 + Selectable, 722 + Clone, 723 + Debug, 724 + PartialEq, 725 + Default, 726 + Serialize, 727 + Deserialize, 728 + )] 729 + #[diesel(primary_key(did))] 730 + #[diesel(table_name = crate::schema::pds::device_account)] 731 + #[diesel(check_for_backend(Sqlite))] 732 + pub struct DeviceAccount { 733 + pub did: String, 734 + #[diesel(column_name = deviceId)] 735 + #[serde(rename = "deviceId")] 736 + pub device_id: String, 737 + #[diesel(column_name = authenticatedAt)] 738 + #[serde(rename = "authenticatedAt")] 739 + pub authenticated_at: DateTime<Utc>, 740 + pub remember: bool, 741 + #[diesel(column_name = authorizedClients)] 742 + #[serde(rename = "authorizedClients")] 743 + pub authorized_clients: String, 581 744 } 582 745 583 746 #[derive( 584 747 Queryable, 585 748 Identifiable, 749 + Insertable, 586 750 Selectable, 587 751 Clone, 588 752 Debug, ··· 591 755 Serialize, 592 756 Deserialize, 593 757 )] 594 - #[diesel(primary_key(refreshToken))] 758 + #[diesel(primary_key(id))] 759 + #[diesel(table_name = crate::schema::pds::authorization_request)] 760 + #[diesel(check_for_backend(Sqlite))] 761 + pub struct AuthorizationRequest { 762 + pub id: String, 763 + pub did: Option<String>, 764 + #[diesel(column_name = deviceId)] 765 + #[serde(rename = "deviceId")] 766 + pub device_id: Option<String>, 767 + #[diesel(column_name = clientId)] 768 + #[serde(rename = "clientId")] 769 + pub client_id: String, 770 + #[diesel(column_name = clientAuth)] 771 + #[serde(rename = "clientAuth")] 772 + pub client_auth: String, 773 + pub parameters: String, 774 + #[diesel(column_name = expiresAt)] 775 + #[serde(rename = "expiresAt")] 776 + pub expires_at: DateTime<Utc>, 777 + pub code: Option<String>, 778 + } 779 + 780 + #[derive( 781 + Queryable, Insertable, Selectable, Clone, Debug, PartialEq, Default, Serialize, Deserialize, 782 + )] 595 783 #[diesel(table_name = crate::schema::pds::used_refresh_token)] 596 784 #[diesel(check_for_backend(Sqlite))] 597 785 pub struct UsedRefreshToken { 598 - pub refreshToken: String, 599 - pub tokenId: String, 786 + #[diesel(column_name = tokenId)] 787 + #[serde(rename = "tokenId")] 788 + pub token_id: String, 789 + #[diesel(column_name = refreshToken)] 790 + #[serde(rename = "refreshToken")] 791 + pub refresh_token: String, 600 792 } 601 793 }
+6 -6
src/schema.rs
··· 106 106 clientId -> Varchar, 107 107 clientAuth -> Varchar, 108 108 parameters -> Varchar, 109 - expiresAt -> Timestamptz, 109 + expiresAt -> TimestamptzSqlite, 110 110 code -> Nullable<Varchar>, 111 111 } 112 112 } ··· 139 139 sessionId -> Nullable<Varchar>, 140 140 userAgent -> Nullable<Varchar>, 141 141 ipAddress -> Varchar, 142 - lastSeenAt -> Timestamptz, 142 + lastSeenAt -> TimestamptzSqlite, 143 143 } 144 144 } 145 145 ··· 147 147 device_account (deviceId, did) { 148 148 did -> Varchar, 149 149 deviceId -> Varchar, 150 - authenticatedAt -> Timestamptz, 150 + authenticatedAt -> TimestamptzSqlite, 151 151 remember -> Bool, 152 152 authorizedClients -> Varchar, 153 153 } ··· 255 255 id -> Varchar, 256 256 did -> Varchar, 257 257 tokenId -> Varchar, 258 - createdAt -> Timestamptz, 259 - updatedAt -> Timestamptz, 260 - expiresAt -> Timestamptz, 258 + createdAt -> TimestamptzSqlite, 259 + updatedAt -> TimestamptzSqlite, 260 + expiresAt -> TimestamptzSqlite, 261 261 clientId -> Varchar, 262 262 clientAuth -> Varchar, 263 263 deviceId -> Nullable<Varchar>,