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

Use background + space theme in standalone chat #1823

Merged
merged 12 commits into from
Jul 8, 2023
2 changes: 2 additions & 0 deletions app/src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Position } from '@holium/design-system';
import { MouseState } from '@holium/realm-presence';

import { ConduitState } from 'os/services/api';
import { migrationPreload } from 'os/services/migration/migration.service';
import { settingsPreload } from 'os/services/ship/settings.service';
import { bazaarPreload } from 'os/services/ship/spaces/bazaar.service';
import { spacesPreload } from 'os/services/ship/spaces/spaces.service';
Expand Down Expand Up @@ -215,6 +216,7 @@ contextBridge.exposeInMainWorld('realm', realmPreload);
contextBridge.exposeInMainWorld('shipService', shipPreload);
contextBridge.exposeInMainWorld('spacesService', spacesPreload);
contextBridge.exposeInMainWorld('authService', authPreload);
contextBridge.exposeInMainWorld('migrationService', migrationPreload);
contextBridge.exposeInMainWorld('chatService', chatPreload);
contextBridge.exposeInMainWorld('walletService', walletPreload);
contextBridge.exposeInMainWorld('notifService', notifPreload);
Expand Down
3 changes: 3 additions & 0 deletions app/src/os/realm.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import AbstractService, { ServiceOptions } from './services/abstract.service';
import { APIConnection } from './services/api';
import { AuthService } from './services/auth/auth.service';
import OnboardingService from './services/auth/onboarding.service';
import { MigrationService } from './services/migration/migration.service';
import { FileUploadParams, ShipService } from './services/ship/ship.service';

export class RealmService extends AbstractService<RealmUpdateTypes> {
public services?: {
auth: AuthService;
onboarding: OnboardingService;
migration: MigrationService;
ship?: ShipService;
};

Expand All @@ -29,6 +31,7 @@ export class RealmService extends AbstractService<RealmUpdateTypes> {
this.services = {
auth: new AuthService(),
onboarding: new OnboardingService(),
migration: new MigrationService(),
};

this.onWebViewAttached = this.onWebViewAttached.bind(this);
Expand Down
37 changes: 32 additions & 5 deletions app/src/os/services/abstract.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,13 @@ abstract class AbstractDataAccess<T, U = unknown> {
return row ? this.mapRow(row) : null;
}

public create(values: Partial<T>): T {
/**
*
* @param values the values to insert into the table. This should also include the primary key, if not auto-incrementing
* @param pKey Optional primary key to use for the record. If not provided, the lastInsertRowId will be used to find the record
* @returns the newly created record
*/
public create(values: Partial<T>, pKey?: number | string): T {
const columns = Object.keys(values).join(', ');
const placeholders = Object.keys(values)
.map(() => '?')
Expand All @@ -85,9 +91,15 @@ abstract class AbstractDataAccess<T, U = unknown> {
const stmt = this.prepare(query);

const result = stmt.run(Object.values(values));
const id = result.lastInsertRowid;
if (!id) throw new Error('Failed to create new record');
const created = this.findOne(id as number);
if (result.changes !== 1) throw new Error('Failed to create record');

let created: T | null = null;
if (typeof pKey !== 'undefined') {
created = this.findOne(pKey);
} else {
const id = result.lastInsertRowid;
created = this.findOne(id as number);
}
if (!created) throw new Error('Failed to create new record');
return created;
}
Expand All @@ -101,15 +113,30 @@ abstract class AbstractDataAccess<T, U = unknown> {

const result = stmt.run([...Object.values(values), pKey]);
if (result.changes !== 1) throw new Error('Failed to update record');

const updated = this.findOne(pKey);
if (!updated) throw new Error('Failed to update record');
return updated;
}

/**
*
* @param pKey key of the record to update or create
* @param values the table values, in proper order, including the primary key
* @returns the created or updated record
*/
public upsert(pKey: number | string, values: Partial<T>): T {
if (this.findOne(pKey)) {
return this.update(pKey, values);
}
return this.create(values, pKey);
}

public delete(pKey: number | string): void {
const query = `DELETE FROM ${this.tableName} WHERE ${this.pKey} = ?`;
const stmt = this.prepare(query);
stmt.run(pKey);
const result = stmt.run(pKey);
if (result.changes !== 1) throw new Error('Failed to delete record');
}

public raw(query: string, params?: any[]): any[] {
Expand Down
55 changes: 4 additions & 51 deletions app/src/os/services/auth/accounts.table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,62 +36,13 @@ export class Accounts extends AbstractDataAccess<DBAccount, any> {
updatedAt: row.updatedAt,
};
}

public findOne(serverId: string): DBAccount | null {
const query = `SELECT * FROM ${this.tableName} WHERE serverId = ?`;
const stmt = this.prepare(query);
const row = stmt.get(serverId);
return row ? this.mapRow(row) : null;
}

public findAll(accountId: number): DBAccount[] {
const query = `SELECT * FROM ${this.tableName} WHERE accountId = ?`;
const stmt = this.prepare(query);
const rows = stmt.all(accountId);
return rows.map((row) => this.mapRow(row));
}

public create(values: Partial<DBAccount>): DBAccount {
const columns = Object.keys(values).join(', ');
const placeholders = Object.keys(values)
.map(() => '?')
.join(', ');
const query = `INSERT INTO ${this.tableName} (${columns}) VALUES (${placeholders})`;
const stmt = this.prepare(query);

stmt.run(Object.values(values));
if (!values.serverId) throw new Error('Failed to create new record');
const created = this.findOne(values.serverId);
if (!created) throw new Error('Failed to create new record');
return created;
}

public update(serverId: string, values: Partial<DBAccount>): DBAccount {
const setClause = Object.keys(values)
.map((key) => `${key} = ?`)
.join(', ');
const query = `UPDATE ${this.tableName} SET ${setClause} WHERE serverId = ?`;
const stmt = this.prepare(query);

stmt.run([...Object.values(values), serverId]);
const updated = this.findOne(serverId);
if (!updated) throw new Error('Failed to update record');
return updated;
}

public delete(serverId: string): void {
const query = `DELETE FROM ${this.tableName} WHERE serverId = ?`;
const stmt = this.prepare(query);

const result = stmt.run(serverId);
if (result.changes !== 1) throw new Error('Failed to delete record');
}
}

export const accountsInit = `
export const accountsInitSql = `
create table if not exists accounts (
accountId INTEGER,
serverId TEXT PRIMARY KEY NOT NULL,
serverCode TEXT,
serverUrl TEXT NOT NULL,
serverType TEXT NOT NULL DEFAULT 'local',
nickname TEXT,
Expand All @@ -108,3 +59,5 @@ export const accountsInit = `
);
create unique index if not exists accounts_server_id_uindex on accounts (serverId);
`;

export const accountsWipeSql = `drop table if exists accounts;`;
163 changes: 46 additions & 117 deletions app/src/os/services/auth/auth.db.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
import { app } from 'electron';
import log from 'electron-log';
import Store from 'electron-store';
import Database from 'better-sqlite3-multiple-ciphers';
import path from 'path';

import { Accounts, accountsInit } from './accounts.table';
import { AuthStore } from './auth.model.old';
import { MasterAccounts, masterAccountsInit } from './masterAccounts.table';
import { Migration, MigrationService } from '../migration/migration.service';
import { Accounts, accountsInitSql, accountsWipeSql } from './accounts.table';
import {
MasterAccounts,
masterAccountsInitSql,
masterAccountsWipeSql,
} from './masterAccounts.table';

const initSql = `
${accountsInitSql}
${masterAccountsInitSql}
create table if not exists accounts_order (
serverId TEXT PRIMARY KEY NOT NULL,
idx INTEGER NOT NULL
);

create table if not exists accounts_meta (
seenSplash INTEGER NOT NULL DEFAULT 0,
migrated INTEGER NOT NULL DEFAULT 0,
migratedAt INTEGER
);
`;

const wipeSql = `
${accountsWipeSql}
${masterAccountsWipeSql}
drop table if exists accounts_order;
drop table if exists accounts_meta;
`;

const migrations: Migration[] = [
{
version: 1,
up: `${initSql}`,
down: `${wipeSql}`,
},
{
version: 2,
up: `ALTER TABLE accounts DROP COLUMN serverCode;`,
down: `ALTER TABLE accounts ADD COLUMN serverCode TEXT;`,
},
];

export class AuthDB {
private readonly authDB: Database;
Expand All @@ -16,17 +52,11 @@ export class AuthDB {
};

constructor() {
// Open the authentication database
this.authDB = new Database(
path.join(app.getPath('userData'), 'auth.sqlite'),
{}
this.authDB = MigrationService.getInstance().setupAndMigrate(
'auth',
migrations,
2
);
this.authDB.pragma('journal_mode = WAL');
this.authDB.pragma('foreign_keys = ON');
this.authDB.exec(initSql);
// Migration and cleanup
// TODO need to define a better migration strategy
this.removeShipCodeColumnIfExist();

this.tables = {
accounts: new Accounts(this.authDB),
Expand All @@ -38,29 +68,6 @@ export class AuthDB {
});
}

private removeShipCodeColumnIfExist(): void {
const query = this.authDB.prepare(`
select count(*) as found from pragma_table_info('accounts') where name='serverCode'
`);
const result = query.all();
const found: boolean = result?.[0].found > 0;
if (found) {
log.info(
'auth.db.ts:',
'Removing "serverCode" column from accounts table'
);
this.authDB.prepare('ALTER TABLE accounts RENAME TO accounts_old;').run();
this.authDB.exec(accountsInit);
this.authDB
.prepare(
'INSERT INTO accounts (accountId, serverUrl, serverId, serverType, nickname, color, avatar, status, theme, passwordHash) SELECT accountId, serverUrl, serverId, serverType, nickname, color, avatar, status, theme, passwordHash FROM accounts_old;'
)

.run();
this.authDB.prepare('DROP TABLE accounts_old;').run();
}
}

hasSeenSplash(): boolean {
const result: any = this.authDB
.prepare('SELECT seenSplash FROM accounts_meta;')
Expand All @@ -77,69 +84,6 @@ export class AuthDB {
.run();
}

_needsMigration(): boolean {
const result: any = this.authDB
.prepare('SELECT migrated FROM accounts_meta LIMIT 1;')
.all();

return !(result[0]?.migrated || null);
}

migrateJsonToSqlite(masterAccountId: number) {
try {
const oldAuth = new Store({
name: 'realm.auth',
accessPropertiesByDotNotation: true,
defaults: AuthStore.create({ firstTime: true }),
});
// if realm.auth is empty, don't migrate
if (!oldAuth.store || Object.keys(oldAuth.store).length === 0) {
log.info('auth.db.ts:', 'No realm.auth.json to migrate -> skipping');
return;
}
log.info('auth.db.ts:', 'Migrating realm.auth.json to sqlite');
const oldTheme = new Store({
name: 'realm.auth-theme',
accessPropertiesByDotNotation: true,
});
// loop through ships and insert into accounts table
oldAuth.store.ships.forEach((ship) => {
const theme = oldTheme.store[ship.patp];
const query = this.authDB.prepare(`
INSERT INTO accounts (accountId, serverUrl, serverId, serverType, nickname, color, avatar, status, theme, passwordHash)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
`);
query.run(
masterAccountId,
ship.url,
ship.patp,
'local',
ship.nickname,
ship.color,
ship.avatar,
ship.status,
theme ? JSON.stringify(theme) : JSON.stringify({}),
ship.passwordHash
);
});
oldAuth.store.order.forEach((serverId: string, index: number) => {
const query = this.authDB.prepare(`
REPLACE INTO accounts_order (serverId, idx)
VALUES (?, ?);
`);
query.run(serverId.replace('auth', ''), index);
});
// TODO clear old auth store
} catch (error) {
log.error(error);
}
this.authDB
.prepare(
'REPLACE INTO accounts_meta (migrated, migratedAt) VALUES (1, ?);'
)
.run(Date.now());
}

public addToOrder(serverId: string): void {
const query = this.authDB.prepare(`
REPLACE INTO accounts_order (serverId, idx)
Expand Down Expand Up @@ -175,18 +119,3 @@ export class AuthDB {
this.authDB.close();
}
}

const initSql = `
${accountsInit}
${masterAccountsInit}
create table if not exists accounts_order (
serverId TEXT PRIMARY KEY NOT NULL,
idx INTEGER NOT NULL
);

create table if not exists accounts_meta (
seenSplash INTEGER NOT NULL DEFAULT 0,
migrated INTEGER NOT NULL DEFAULT 0,
migratedAt INTEGER
);
`;
4 changes: 3 additions & 1 deletion app/src/os/services/auth/masterAccounts.table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class MasterAccounts extends AbstractDataAccess<MasterAccount> {
}
}

export const masterAccountsInit = `
export const masterAccountsInitSql = `
create table if not exists master_accounts (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL,
Expand All @@ -41,3 +41,5 @@ export const masterAccountsInit = `
);
create unique index if not exists master_email_uindex on master_accounts (email);
`;

export const masterAccountsWipeSql = `drop table if exists master_accounts;`;
Loading