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

Kyesly integration #338

Merged
merged 11 commits into from
Nov 4, 2023
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ COPY --chown=node:node --from=build /usr/src/app/domain_model domain_model
COPY --chown=node:node --from=build /usr/src/app/frontend/build /usr/src/app/frontend/build
COPY --chown=node:node --from=build /usr/src/app/backend/build /usr/src/app/backend/build
COPY --chown=node:node --from=build /usr/src/app/backend/node_modules /usr/src/app/backend/node_modules
CMD ["dumb-init", "node", "backend/build/backend/src/index.js"]

ENTRYPOINT ["dumb-init", "--"]
CMD ["npm", "run", "start"]
EXPOSE 5000
299 changes: 240 additions & 59 deletions backend/package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"test": "jest --forceExit --detectOpenHandles",
"start": "npm run-script build && node ./build/backend/src/index.js",
"dev": "nodemon ./src/index.ts",
"build": "tsc --project ./"
"build": "tsc --project ./",
"migrate:latest": "node ./build/backend/src/migrate-to-latest.js"
},
"keywords": [],
"author": "",
Expand All @@ -18,9 +19,10 @@
"@types/cookie-parser": "^1.4.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.1",
"@types/luxon": "^3.3.0",
"@types/node": "^16.11.26",
"@types/jest": "^27.4.1",
"@types/pg": "^8.10.2",
"aws-sdk": "^2.1277.0",
"axios": "^0.24.0",
"cookie-parser": "^1.4.6",
Expand All @@ -31,6 +33,7 @@
"express-async-handler": "^1.2.0",
"fraction.js": "^4.2.0",
"jsonwebtoken": "^9.0.0",
"kysely": "^0.25.0",
"luxon": "^3.3.0",
"multer": "^1.4.5-lts.1",
"node-fetch": "^3.2.3",
Expand Down
58 changes: 58 additions & 0 deletions backend/src/Migrations/2023_07_03_Initial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Kysely } from 'kysely'

export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('electionDB')
.addColumn('election_id', 'varchar', (col) => col.primaryKey())
.addColumn('title', 'varchar')
.addColumn('description', 'text')
.addColumn('frontend_url', 'varchar')
.addColumn('start_time', 'varchar')
.addColumn('end_time', 'varchar')
.addColumn('support_email', 'varchar')
.addColumn('owner_id', 'varchar')
.addColumn('audit_ids', 'json')
.addColumn('admin_ids', 'json')
.addColumn('credential_ids', 'json')
.addColumn('state', 'varchar')
.addColumn('races', 'json', (col) => col.notNull())
.addColumn('settings', 'json')
.addColumn('auth_key', 'varchar')
.execute()

await db.schema
.createTable('electionRollDB')
.addColumn('voter_id', 'varchar', (col) => col.primaryKey().notNull())
.addColumn('election_id', 'varchar', (col) => col.notNull())
.addColumn('email', 'varchar')
.addColumn('submitted', 'boolean', (col) => col.notNull())
.addColumn('ballot_id', 'varchar')
.addColumn('ip_address', 'varchar')
.addColumn('address', 'varchar')
.addColumn('state', 'varchar', (col) => col.notNull())
.addColumn('history', 'json')
.addColumn('registration', 'json')
.addColumn('precinct', 'varchar')
.addColumn('email_data', 'json')
.execute()

await db.schema
.createTable('ballotDB')
.addColumn('ballot_id', 'varchar', (col) => col.primaryKey().notNull())
.addColumn('election_id', 'varchar')
.addColumn('user_id', 'varchar')
.addColumn('status', 'varchar')
.addColumn('date_submitted', 'varchar')
.addColumn('ip_address', 'varchar')
.addColumn('votes', 'json', (col) => col.notNull())
.addColumn('history', 'json')
.addColumn('precinct', 'varchar')
.execute()
}

export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('electionDB').execute()
await db.schema.dropTable('electionRollDB').execute()
await db.schema.dropTable('ballotDB').execute()
}

167 changes: 54 additions & 113 deletions backend/src/Models/Ballots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,146 +3,87 @@ import { Uid } from '../../../domain_model/Uid';
import { ILoggingContext } from '../Services/Logging/ILogger';
import Logger from '../Services/Logging/Logger';
import { IBallotStore } from './IBallotStore';
const className = 'BallotsDB';
import { Kysely, sql } from 'kysely';
import { Database } from './Database';
import { InternalServerError } from '@curveball/http-errors';

export default class BallotsDB implements IBallotStore {
const tableName = 'ballotDB';

export default class BallotsDB implements IBallotStore {
_postgresClient;
_tableName: string;
_tableName: string = tableName;

constructor(postgresClient:any) {
this._tableName = "ballotDB";
constructor(postgresClient: Kysely<Database>) {
this._postgresClient = postgresClient;
this.init();
this.init()
}

async init(): Promise<IBallotStore> {
var appInitContext = Logger.createContext("appInit");
Logger.debug(appInitContext, "BallotsDB.init");
//await this.dropTable(appInitContext);
var query = `
CREATE TABLE IF NOT EXISTS ${this._tableName} (
ballot_id VARCHAR PRIMARY KEY,
election_id VARCHAR,
user_id VARCHAR,
status VARCHAR,
date_submitted VARCHAR,
ip_address VARCHAR,
votes json NOT NULL,
history json,
precinct VARCHAR
);
`;
Logger.debug(appInitContext, query);
var p = this._postgresClient.query(query);
return p.then((_: any) => {
//This will add the new field to the live DB in prod. Once that's done we can remove this
var historyQuery = `
ALTER TABLE ${this._tableName} ADD COLUMN IF NOT EXISTS precinct VARCHAR
`;
return this._postgresClient.query(historyQuery).catch((err:any) => {
Logger.error(appInitContext, "err adding precinct column to DB: " + err.message);
return err;
});
}).then((_:any)=> {
return this;
});
return this;
}

async dropTable(ctx:ILoggingContext):Promise<void>{
var query = `DROP TABLE IF EXISTS ${this._tableName};`;
var p = this._postgresClient.query({
text: query,
});
return p.then((_: any) => {
Logger.debug(ctx, `Dropped it (like its hot)`);
});
async dropTable(ctx: ILoggingContext): Promise<void> {
Logger.debug(ctx, `${tableName}.dropTable`);
return this._postgresClient.schema.dropTable(tableName).execute()
}

submitBallot(ballot: Ballot, ctx:ILoggingContext, reason:string): Promise<Ballot> {
Logger.debug(ctx, `${className}.submit`, ballot);
var sqlString = `INSERT INTO ${this._tableName} (ballot_id,election_id,user_id,status,date_submitted,ip_address,votes,history,precinct)
VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING ballot_id;`;
Logger.debug(ctx, sqlString);
var p = this._postgresClient.query({
rowMode: 'array',
text: sqlString,
values: [
ballot.ballot_id,
ballot.election_id,
ballot.user_id,
ballot.status,
ballot.date_submitted,
ballot.ip_address,
JSON.stringify(ballot.votes),
JSON.stringify(ballot.history),
ballot.precinct]
});
submitBallot(ballot: Ballot, ctx: ILoggingContext, reason: string): Promise<Ballot> {
Logger.debug(ctx, `${tableName}.submit`, ballot);

return p.then((res: any) => {
Logger.debug(ctx, `set response rows:`, res);
ballot.ballot_id = res.rows[0][0];
Logger.state(ctx, `Ballot submitted`, { ballot: ballot, reason: reason});
return ballot;
});
return this._postgresClient
.insertInto(tableName)
.values(ballot)
.returningAll()
.executeTakeFirstOrThrow()
.then((ballot) => {
Logger.state(ctx, `Ballot submitted`, { ballot: ballot, reason: reason });
return ballot;
});
}

getBallotByID(ballot_id: string, ctx:ILoggingContext): Promise<Ballot | null> {
Logger.debug(ctx, `${className}.getBallotByID ${ballot_id}`);
var sqlString = `SELECT * FROM ${this._tableName} WHERE ballot_id = $1`;
Logger.debug(ctx, sqlString);
getBallotByID(ballot_id: string, ctx: ILoggingContext): Promise<Ballot | null> {
Logger.debug(ctx, `${tableName}.getBallotByID ${ballot_id}`);

var p = this._postgresClient.query({
text: sqlString,
values: [ballot_id]
});
return p.then((response: any) => {
var rows = response.rows;
if (rows.length == 0) {
Logger.debug(ctx, `.get null`);
return this._postgresClient
.selectFrom(tableName)
.selectAll()
.where('ballot_id', '=', ballot_id)
.executeTakeFirstOrThrow()
.catch((reason: any) => {
Logger.debug(ctx, `${tableName}.get null`, reason);
return null;
}
return rows[0] as Ballot;
});
})
}


getBallotsByElectionID(election_id: string, ctx:ILoggingContext): Promise<Ballot[] | null> {
Logger.debug(ctx, `${className}.getBallotsByElectionID ${election_id}`);
var sqlString = `SELECT * FROM ${this._tableName} WHERE election_id = $1`;
Logger.debug(ctx, sqlString);
getBallotsByElectionID(election_id: string, ctx: ILoggingContext): Promise<Ballot[] | null> {
Logger.debug(ctx, `${tableName}.getBallotsByElectionID ${election_id}`);

var p = this._postgresClient.query({
text: sqlString,
values: [election_id]
});
return p.then((response: any) => {
var rows = response.rows;
console.log(rows[0])
if (rows.length == 0) {
Logger.debug(ctx, `.get null`);
return [] as Ballot[];
}
return rows
});
return this._postgresClient
.selectFrom(tableName)
.selectAll()
.where('election_id', '=', election_id)
.execute()
}

delete(ballot_id: Uid, ctx:ILoggingContext, reason:string): Promise<boolean> {
Logger.debug(ctx, `${className}.delete ${ballot_id}`);
delete(ballot_id: Uid, ctx: ILoggingContext, reason: string): Promise<boolean> {
Logger.debug(ctx, `${tableName}.delete ${ballot_id}`);
var sqlString = `DELETE FROM ${this._tableName} WHERE ballot_id = $1`;
Logger.debug(ctx, sqlString);

var p = this._postgresClient.query({
rowMode: 'array',
text: sqlString,
values: [ballot_id]
});
return p.then((response: any) => {
if (response.rowCount == 1) {
Logger.state(ctx, `Ballot ${ballot_id} deleted:`, {ballotId: ballot_id, reason: reason });
return true;
}
return false;
});
return this._postgresClient
.deleteFrom(tableName)
.where('ballot_id', '=', ballot_id)
.returningAll()
.executeTakeFirst()
.then((ballot) => {
if (ballot) {
return true
} else {
return false
}
})
}
}
9 changes: 9 additions & 0 deletions backend/src/Models/Database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Ballot } from "../../../domain_model/Ballot";
import { Election } from "../../../domain_model/Election";
import { ElectionRoll } from "../../../domain_model/ElectionRoll";

export interface Database {
electionDB: Election,
electionRollDB: ElectionRoll
ballotDB: Ballot
}
Loading