Skip to content
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

feat: push objects on client sync #81

Merged
merged 2 commits into from
Mar 11, 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
39 changes: 38 additions & 1 deletion apps/cacvote-jx-terminal/backend/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use base64_serde::base64_serde_type;
use sqlx::postgres::PgPoolOptions;
use sqlx::{Connection, PgPool};
use tracing::Level;
use types_rs::cacvote::{JournalEntry, JurisdictionCode};
use types_rs::cacvote::{JournalEntry, JurisdictionCode, SignedObject};

use crate::config::Config;

Expand Down Expand Up @@ -81,3 +81,40 @@ pub(crate) async fn get_latest_journal_entry(
.fetch_optional(&mut *connection)
.await?)
}

pub(crate) async fn get_unsynced_objects(
executor: &mut sqlx::PgConnection,
) -> color_eyre::eyre::Result<Vec<SignedObject>> {
Ok(sqlx::query_as!(
SignedObject,
r#"
SELECT
id,
payload,
certificates,
signature
FROM objects
WHERE server_synced_at IS NULL
"#,
)
.fetch_all(&mut *executor)
.await?)
}

pub(crate) async fn mark_object_synced(
executor: &mut sqlx::PgConnection,
id: uuid::Uuid,
) -> color_eyre::eyre::Result<()> {
sqlx::query!(
r#"
UPDATE objects
SET server_synced_at = now()
WHERE id = $1
"#,
id
)
.execute(&mut *executor)
.await?;

Ok(())
}
23 changes: 23 additions & 0 deletions apps/cacvote-jx-terminal/backend/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ pub(crate) async fn sync(
) -> color_eyre::eyre::Result<()> {
client.check_status().await?;

push_objects(executor, client).await?;
pull_journal_entries(executor, client).await?;

Ok(())
}

async fn pull_journal_entries(
executor: &mut sqlx::PgConnection,
client: &Client,
) -> color_eyre::eyre::Result<()> {
let latest_journal_entry_id = db::get_latest_journal_entry(executor)
.await?
.map(|entry| entry.id);
Expand All @@ -54,3 +64,16 @@ pub(crate) async fn sync(

Ok(())
}

async fn push_objects(
executor: &mut sqlx::PgConnection,
client: &Client,
) -> color_eyre::eyre::Result<()> {
let objects = db::get_unsynced_objects(executor).await?;
for object in objects {
let object_id = client.create_object(object).await?;
db::mark_object_synced(executor, object_id).await?;
}

Ok(())
}
17 changes: 17 additions & 0 deletions apps/cacvote-mark/backend/bin/create-object
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env node

require('esbuild-runner/register');

require('../src/bin/create-object/main')
.main(process.argv, {
stdin: process.stdin,
stdout: process.stdout,
stderr: process.stderr,
})
.then((code) => {
process.exitCode = code;
})
.catch((err) => {
console.error(err.stack);
process.exit(1);
});
1 change: 1 addition & 0 deletions apps/cacvote-mark/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"@types/uuid": "9.0.5",
"@types/xml": "^1.0.9",
"@votingworks/test-utils": "workspace:*",
"esbuild-runner": "2.2.2",
"eslint": "8.51.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-node": "^0.3.9",
Expand Down
55 changes: 55 additions & 0 deletions apps/cacvote-mark/backend/src/bin/create-object/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Buffer } from 'buffer';
import { readFile } from 'fs/promises';
import { cryptography } from '@votingworks/auth';
import { v4 } from 'uuid';
import { Readable } from 'stream';
import { unsafeParse } from '@votingworks/types';
import { join } from 'path';
import { Payload, SignedObject, UuidSchema } from '../../cacvote-server/types';
import { resolveWorkspace } from '../../workspace';

const DEV_CERTS_PATH = join(__dirname, '../../../../../../libs/auth/certs/dev');
const PRIVATE_KEY_PATH = join(DEV_CERTS_PATH, 'vx-admin-private-key.pem');
const VX_ADMIN_CERT_AUTHORITY_CERT_PATH = join(
DEV_CERTS_PATH,
'vx-admin-cert-authority-cert.pem'
);

export async function main(): Promise<void> {
const workspace = await resolveWorkspace();

interface TestObject {
name: string;
description: string;
value: number;
}

const object: TestObject = {
name: 'Test Object',
description: 'This is a test object',
value: 42,
};

const payload = new Payload(
'TestObject',
Buffer.from(JSON.stringify(object))
);

const certificatesPem = await readFile(VX_ADMIN_CERT_AUTHORITY_CERT_PATH);
const payloadBuffer = Buffer.from(JSON.stringify(payload));
const signature = await cryptography.signMessage({
message: Readable.from(payloadBuffer),
signingPrivateKey: {
source: 'file',
path: PRIVATE_KEY_PATH,
},
});
const signedObject = new SignedObject(
unsafeParse(UuidSchema, v4()),
payloadBuffer,
certificatesPem,
signature
);

console.log(await workspace.store.addObject(signedObject));
}
Loading