2023-08-21 13:32:06 +05:00
|
|
|
/*
|
|
|
|
|
This file is part of the Notesnook project (https://notesnook.com/)
|
|
|
|
|
|
|
|
|
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
|
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { Notes } from "../collections/notes";
|
|
|
|
|
import { Crypto, CryptoAccessor } from "../database/crypto";
|
|
|
|
|
import { FileStorage, FileStorageAccessor } from "../database/fs";
|
|
|
|
|
import { Notebooks } from "../collections/notebooks";
|
|
|
|
|
import Trash from "../collections/trash";
|
|
|
|
|
import Sync, { SyncOptions } from "./sync";
|
2023-10-12 10:29:11 +05:00
|
|
|
import { Tags } from "../collections/tags";
|
|
|
|
|
import { Colors } from "../collections/colors";
|
2023-08-21 13:32:06 +05:00
|
|
|
import Vault from "./vault";
|
|
|
|
|
import Lookup from "./lookup";
|
|
|
|
|
import { Content } from "../collections/content";
|
|
|
|
|
import Backup from "../database/backup";
|
|
|
|
|
import Hosts from "../utils/constants";
|
|
|
|
|
import { EV, EVENTS } from "../common";
|
2023-09-16 14:35:09 +05:00
|
|
|
import { LegacySettings } from "../collections/legacy-settings";
|
2023-08-21 13:32:06 +05:00
|
|
|
import Migrations from "./migrations";
|
|
|
|
|
import UserManager from "./user-manager";
|
|
|
|
|
import http from "../utils/http";
|
2023-09-18 12:37:30 +05:00
|
|
|
import { Monographs } from "./monographs";
|
2023-08-21 13:32:06 +05:00
|
|
|
import { Offers } from "./offers";
|
|
|
|
|
import { Attachments } from "../collections/attachments";
|
|
|
|
|
import { Debug } from "./debug";
|
|
|
|
|
import { Mutex } from "async-mutex";
|
|
|
|
|
import { NoteHistory } from "../collections/note-history";
|
|
|
|
|
import MFAManager from "./mfa-manager";
|
|
|
|
|
import EventManager from "../utils/event-manager";
|
|
|
|
|
import { Pricing } from "./pricing";
|
|
|
|
|
import { logger } from "../logger";
|
|
|
|
|
import { Shortcuts } from "../collections/shortcuts";
|
|
|
|
|
import { Reminders } from "../collections/reminders";
|
|
|
|
|
import { Relations } from "../collections/relations";
|
|
|
|
|
import Subscriptions from "./subscriptions";
|
|
|
|
|
import {
|
|
|
|
|
CompressorAccessor,
|
|
|
|
|
ICompressor,
|
|
|
|
|
IFileStorage,
|
|
|
|
|
IStorage,
|
2024-02-05 22:05:55 +05:00
|
|
|
KVStorageAccessor,
|
2023-08-21 13:32:06 +05:00
|
|
|
StorageAccessor
|
|
|
|
|
} from "../interfaces";
|
|
|
|
|
import TokenManager from "./token-manager";
|
|
|
|
|
import { Attachment } from "../types";
|
2023-09-16 14:35:09 +05:00
|
|
|
import { Settings } from "../collections/settings";
|
2023-10-24 11:39:12 +05:00
|
|
|
import {
|
|
|
|
|
DatabaseAccessor,
|
|
|
|
|
DatabaseSchema,
|
2024-05-20 13:02:38 +05:00
|
|
|
RawDatabaseSchema,
|
2023-10-24 11:39:12 +05:00
|
|
|
SQLiteOptions,
|
2023-12-28 14:35:15 +05:00
|
|
|
changeDatabasePassword,
|
2024-03-29 15:38:17 +05:00
|
|
|
createDatabase,
|
|
|
|
|
initializeDatabase
|
2023-10-24 11:39:12 +05:00
|
|
|
} from "../database";
|
2023-12-12 10:02:26 +05:00
|
|
|
import { Kysely, Transaction, sql } from "kysely";
|
2023-10-07 13:26:03 +05:00
|
|
|
import { CachedCollection } from "../database/cached-collection";
|
2024-02-03 16:24:07 +05:00
|
|
|
import { Vaults } from "../collections/vaults";
|
2024-02-05 22:05:55 +05:00
|
|
|
import { KVStorage } from "../database/kv";
|
2024-02-10 10:59:27 +05:00
|
|
|
import { QueueValue } from "../utils/queue-value";
|
2024-03-11 22:41:36 +05:00
|
|
|
import { Sanitizer } from "../database/sanitizer";
|
2024-05-20 13:02:38 +05:00
|
|
|
import { createTriggers, dropTriggers } from "../database/triggers";
|
|
|
|
|
import { NNMigrationProvider } from "../database/migrations";
|
2023-08-21 13:32:06 +05:00
|
|
|
|
|
|
|
|
type EventSourceConstructor = new (
|
|
|
|
|
uri: string,
|
|
|
|
|
init: EventSourceInit & { headers?: Record<string, string> }
|
|
|
|
|
) => EventSource;
|
|
|
|
|
type Options = {
|
2023-10-24 11:39:12 +05:00
|
|
|
sqliteOptions: SQLiteOptions;
|
2023-08-21 13:32:06 +05:00
|
|
|
storage: IStorage;
|
|
|
|
|
eventsource?: EventSourceConstructor;
|
|
|
|
|
fs: IFileStorage;
|
|
|
|
|
compressor: ICompressor;
|
2023-10-24 11:39:12 +05:00
|
|
|
batchSize: number;
|
2023-08-21 13:32:06 +05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// const DIFFERENCE_THRESHOLD = 20 * 1000;
|
|
|
|
|
// const MAX_TIME_ERROR_FAILURES = 5;
|
|
|
|
|
class Database {
|
|
|
|
|
isInitialized = false;
|
|
|
|
|
eventManager = new EventManager();
|
|
|
|
|
sseMutex = new Mutex();
|
2024-05-07 12:57:45 +05:00
|
|
|
_fs?: FileStorage;
|
2023-08-21 13:32:06 +05:00
|
|
|
|
|
|
|
|
storage: StorageAccessor = () => {
|
|
|
|
|
if (!this.options?.storage)
|
|
|
|
|
throw new Error(
|
|
|
|
|
"Database not initialized. Did you forget to call db.setup()?"
|
|
|
|
|
);
|
|
|
|
|
return this.options.storage;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fs: FileStorageAccessor = () => {
|
|
|
|
|
if (!this.options?.fs)
|
|
|
|
|
throw new Error(
|
|
|
|
|
"Database not initialized. Did you forget to call db.setup()?"
|
|
|
|
|
);
|
2024-05-07 12:57:45 +05:00
|
|
|
return (
|
|
|
|
|
this._fs ||
|
|
|
|
|
(this._fs = new FileStorage(this.options.fs, this.tokenManager))
|
|
|
|
|
);
|
2023-08-21 13:32:06 +05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
crypto: CryptoAccessor = () => {
|
|
|
|
|
if (!this.options)
|
|
|
|
|
throw new Error(
|
|
|
|
|
"Database not initialized. Did you forget to call db.setup()?"
|
|
|
|
|
);
|
|
|
|
|
return new Crypto(this.storage);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
compressor: CompressorAccessor = () => {
|
|
|
|
|
if (!this.options?.compressor)
|
|
|
|
|
throw new Error(
|
|
|
|
|
"Database not initialized. Did you forget to call db.setup()?"
|
|
|
|
|
);
|
|
|
|
|
return this.options.compressor;
|
|
|
|
|
};
|
|
|
|
|
|
2023-10-02 09:40:10 +05:00
|
|
|
private _sql?: Kysely<DatabaseSchema>;
|
|
|
|
|
sql: DatabaseAccessor = () => {
|
2024-04-22 12:23:43 +05:00
|
|
|
// if (this._transaction) return this._transaction.value;
|
2023-10-02 09:40:10 +05:00
|
|
|
|
|
|
|
|
if (!this._sql)
|
|
|
|
|
throw new Error(
|
|
|
|
|
"Database not initialized. Did you forget to call db.init()?"
|
|
|
|
|
);
|
|
|
|
|
return this._sql;
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-05 22:05:55 +05:00
|
|
|
private _kv?: KVStorage;
|
|
|
|
|
kv: KVStorageAccessor = () => this._kv || new KVStorage(this.sql);
|
|
|
|
|
|
2024-02-10 10:59:27 +05:00
|
|
|
private _transaction?: QueueValue<Transaction<DatabaseSchema>>;
|
|
|
|
|
transaction = async (
|
2024-04-22 12:23:43 +05:00
|
|
|
executor: (tr: Kysely<DatabaseSchema>) => Promise<void>
|
2023-10-02 09:40:10 +05:00
|
|
|
) => {
|
2024-04-22 12:23:43 +05:00
|
|
|
await executor(this.sql());
|
|
|
|
|
// if (this._transaction) {
|
|
|
|
|
// await executor(this._transaction.use()).finally(() =>
|
|
|
|
|
// this._transaction?.discard()
|
|
|
|
|
// );
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// return this.sql()
|
|
|
|
|
// .transaction()
|
|
|
|
|
// .execute(async (tr) => {
|
|
|
|
|
// this._transaction = new QueueValue(
|
|
|
|
|
// tr,
|
|
|
|
|
// () => (this._transaction = undefined)
|
|
|
|
|
// );
|
|
|
|
|
// await executor(this._transaction.use());
|
|
|
|
|
// })
|
|
|
|
|
// .finally(() => this._transaction?.discard());
|
2023-10-02 09:40:10 +05:00
|
|
|
};
|
|
|
|
|
|
2023-11-11 13:05:03 +05:00
|
|
|
options!: Options;
|
2023-08-21 13:32:06 +05:00
|
|
|
EventSource?: EventSourceConstructor;
|
|
|
|
|
eventSource?: EventSource | null;
|
|
|
|
|
|
2024-02-05 22:05:55 +05:00
|
|
|
tokenManager = new TokenManager(this.kv);
|
|
|
|
|
mfa = new MFAManager(this.tokenManager);
|
2023-08-21 13:32:06 +05:00
|
|
|
subscriptions = new Subscriptions(this.tokenManager);
|
|
|
|
|
offers = new Offers();
|
|
|
|
|
debug = new Debug();
|
2024-05-14 13:24:48 +05:00
|
|
|
pricing = Pricing;
|
2023-08-21 13:32:06 +05:00
|
|
|
|
|
|
|
|
user = new UserManager(this);
|
|
|
|
|
syncer = new Sync(this);
|
|
|
|
|
vault = new Vault(this);
|
|
|
|
|
lookup = new Lookup(this);
|
|
|
|
|
backup = new Backup(this);
|
|
|
|
|
migrations = new Migrations(this);
|
|
|
|
|
monographs = new Monographs(this);
|
|
|
|
|
trash = new Trash(this);
|
2024-03-11 22:41:36 +05:00
|
|
|
sanitizer = new Sanitizer(this.sql);
|
2023-08-21 13:32:06 +05:00
|
|
|
|
|
|
|
|
notebooks = new Notebooks(this);
|
|
|
|
|
tags = new Tags(this);
|
|
|
|
|
colors = new Colors(this);
|
|
|
|
|
content = new Content(this);
|
|
|
|
|
attachments = new Attachments(this);
|
|
|
|
|
noteHistory = new NoteHistory(this);
|
|
|
|
|
shortcuts = new Shortcuts(this);
|
|
|
|
|
reminders = new Reminders(this);
|
|
|
|
|
relations = new Relations(this);
|
|
|
|
|
notes = new Notes(this);
|
2024-02-03 16:24:07 +05:00
|
|
|
vaults = new Vaults(this);
|
2024-03-11 22:41:36 +05:00
|
|
|
settings = new Settings(this);
|
2023-10-02 09:40:10 +05:00
|
|
|
|
2023-10-07 13:26:03 +05:00
|
|
|
/**
|
|
|
|
|
* @deprecated only kept here for migration purposes
|
|
|
|
|
*/
|
2024-02-05 22:05:55 +05:00
|
|
|
legacyTags = new CachedCollection(this.storage, "tags");
|
2023-10-07 13:26:03 +05:00
|
|
|
/**
|
|
|
|
|
* @deprecated only kept here for migration purposes
|
|
|
|
|
*/
|
2024-02-05 22:05:55 +05:00
|
|
|
legacyColors = new CachedCollection(this.storage, "colors");
|
2023-10-07 13:26:03 +05:00
|
|
|
/**
|
|
|
|
|
* @deprecated only kept here for migration purposes
|
|
|
|
|
*/
|
2024-02-05 22:05:55 +05:00
|
|
|
legacyNotes = new CachedCollection(this.storage, "notes");
|
2023-10-24 11:39:12 +05:00
|
|
|
/**
|
|
|
|
|
* @deprecated only kept here for migration purposes
|
|
|
|
|
*/
|
|
|
|
|
legacySettings = new LegacySettings(this);
|
2023-08-21 13:32:06 +05:00
|
|
|
// constructor() {
|
|
|
|
|
// this.sseMutex = new Mutex();
|
|
|
|
|
// // this.lastHeartbeat = undefined; // { local: 0, server: 0 };
|
|
|
|
|
// // this.timeErrorFailures = 0;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
setup(options: Options) {
|
|
|
|
|
this.options = options;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-12 10:02:26 +05:00
|
|
|
async reset() {
|
|
|
|
|
await this.storage().clear();
|
|
|
|
|
|
2024-03-29 15:38:17 +05:00
|
|
|
await dropTriggers(this.sql());
|
2023-12-12 10:02:26 +05:00
|
|
|
for (const statement of [
|
|
|
|
|
"PRAGMA writable_schema = 1",
|
|
|
|
|
"DELETE FROM sqlite_master",
|
|
|
|
|
"PRAGMA writable_schema = 0",
|
|
|
|
|
"VACUUM",
|
2024-02-10 19:49:30 +05:00
|
|
|
"PRAGMA integrity_check"
|
2023-12-12 10:02:26 +05:00
|
|
|
]) {
|
|
|
|
|
await sql.raw(statement).execute(this.sql());
|
|
|
|
|
}
|
2024-03-29 15:38:17 +05:00
|
|
|
|
2024-05-20 13:02:38 +05:00
|
|
|
await initializeDatabase(
|
|
|
|
|
this.sql().withTables(),
|
|
|
|
|
new NNMigrationProvider()
|
|
|
|
|
);
|
2024-03-29 15:38:17 +05:00
|
|
|
await this.initCollections();
|
|
|
|
|
return true;
|
2023-12-12 10:02:26 +05:00
|
|
|
}
|
|
|
|
|
|
2023-12-28 14:35:15 +05:00
|
|
|
async changePassword(password?: string) {
|
|
|
|
|
if (!this._sql) return;
|
|
|
|
|
await changeDatabasePassword(this._sql, password);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-21 13:32:06 +05:00
|
|
|
async init() {
|
2023-11-11 13:05:03 +05:00
|
|
|
if (!this.options)
|
|
|
|
|
throw new Error(
|
|
|
|
|
"options not specified. Did you forget to call db.setup()?"
|
|
|
|
|
);
|
|
|
|
|
|
2023-08-21 13:32:06 +05:00
|
|
|
EV.subscribeMulti(
|
|
|
|
|
[EVENTS.userLoggedIn, EVENTS.userFetched, EVENTS.tokenRefreshed],
|
|
|
|
|
this.connectSSE,
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
EV.subscribe(EVENTS.attachmentDeleted, async (attachment: Attachment) => {
|
2024-02-03 12:35:28 +05:00
|
|
|
await this.fs().cancel(attachment.hash);
|
2023-08-21 13:32:06 +05:00
|
|
|
});
|
|
|
|
|
EV.subscribe(EVENTS.userLoggedOut, async () => {
|
2023-09-18 12:37:30 +05:00
|
|
|
await this.monographs.clear();
|
2023-08-21 13:32:06 +05:00
|
|
|
await this.fs().clear();
|
|
|
|
|
this.disconnectSSE();
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-20 13:02:38 +05:00
|
|
|
this._sql = (await createDatabase<RawDatabaseSchema>("notesnook", {
|
|
|
|
|
...this.options.sqliteOptions,
|
|
|
|
|
migrationProvider: new NNMigrationProvider(),
|
|
|
|
|
onInit: (db) => createTriggers(db)
|
|
|
|
|
})) as unknown as Kysely<DatabaseSchema>;
|
2023-10-02 09:40:10 +05:00
|
|
|
|
2024-03-11 22:41:36 +05:00
|
|
|
await this.sanitizer.init();
|
|
|
|
|
|
2023-08-21 13:32:06 +05:00
|
|
|
await this.initCollections();
|
|
|
|
|
|
|
|
|
|
await this.migrations.init();
|
|
|
|
|
this.isInitialized = true;
|
|
|
|
|
if (this.migrations.required()) {
|
|
|
|
|
logger.warn("Database migration is required.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async initCollections() {
|
2023-09-16 14:35:09 +05:00
|
|
|
await this.legacySettings.init();
|
2023-08-21 13:32:06 +05:00
|
|
|
// collections
|
|
|
|
|
|
2023-09-16 14:35:09 +05:00
|
|
|
await this.settings.init();
|
2023-08-21 13:32:06 +05:00
|
|
|
await this.notebooks.init();
|
|
|
|
|
await this.tags.init();
|
|
|
|
|
await this.colors.init();
|
|
|
|
|
await this.content.init();
|
|
|
|
|
await this.attachments.init();
|
|
|
|
|
await this.noteHistory.init();
|
|
|
|
|
await this.shortcuts.init();
|
|
|
|
|
await this.reminders.init();
|
|
|
|
|
await this.relations.init();
|
|
|
|
|
await this.notes.init();
|
2024-03-11 22:41:36 +05:00
|
|
|
await this.vaults.init();
|
2023-08-21 13:32:06 +05:00
|
|
|
|
|
|
|
|
await this.trash.init();
|
|
|
|
|
|
2023-10-07 13:26:03 +05:00
|
|
|
// legacy collections
|
|
|
|
|
await this.legacyTags.init();
|
|
|
|
|
await this.legacyColors.init();
|
|
|
|
|
await this.legacyNotes.init();
|
|
|
|
|
|
2023-09-18 12:37:30 +05:00
|
|
|
// we must not wait on network requests that's why
|
|
|
|
|
// no await
|
2024-04-18 09:37:21 +05:00
|
|
|
this.monographs.refresh().catch(logger.error);
|
2023-08-21 13:32:06 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
disconnectSSE() {
|
|
|
|
|
if (!this.eventSource) return;
|
|
|
|
|
this.eventSource.onopen = null;
|
|
|
|
|
this.eventSource.onmessage = null;
|
|
|
|
|
this.eventSource.onerror = null;
|
|
|
|
|
this.eventSource.close();
|
|
|
|
|
this.eventSource = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {{force: boolean, error: any}} args
|
|
|
|
|
*/
|
|
|
|
|
async connectSSE(args?: { force: boolean }) {
|
|
|
|
|
await this.sseMutex.runExclusive(async () => {
|
|
|
|
|
this.eventManager.publish(EVENTS.databaseSyncRequested, true, false);
|
|
|
|
|
|
|
|
|
|
const forceReconnect = args && args.force;
|
|
|
|
|
if (
|
|
|
|
|
!this.EventSource ||
|
|
|
|
|
(!forceReconnect &&
|
|
|
|
|
this.eventSource &&
|
|
|
|
|
this.eventSource.readyState === this.eventSource.OPEN)
|
|
|
|
|
)
|
|
|
|
|
return;
|
|
|
|
|
this.disconnectSSE();
|
|
|
|
|
|
|
|
|
|
const token = await this.tokenManager.getAccessToken();
|
|
|
|
|
if (!token) return;
|
|
|
|
|
|
|
|
|
|
this.eventSource = new this.EventSource(`${Hosts.SSE_HOST}/sse`, {
|
|
|
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.eventSource.onopen = async () => {
|
|
|
|
|
console.log("SSE: opened channel successfully!");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.eventSource.onerror = function (error) {
|
|
|
|
|
console.log("SSE: error:", error);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.eventSource.onmessage = async (event) => {
|
|
|
|
|
try {
|
|
|
|
|
const message = JSON.parse(event.data);
|
|
|
|
|
const data = JSON.parse(message.data);
|
|
|
|
|
switch (message.type) {
|
|
|
|
|
case "upgrade": {
|
|
|
|
|
const user = await this.user.getUser();
|
|
|
|
|
if (!user) break;
|
|
|
|
|
user.subscription = data;
|
|
|
|
|
await this.user.setUser(user);
|
|
|
|
|
EV.publish(EVENTS.userSubscriptionUpdated, data);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "logout": {
|
|
|
|
|
await this.user.logout(true, data.reason || "Unknown.");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "emailConfirmed": {
|
|
|
|
|
await this.tokenManager._refreshToken(true);
|
|
|
|
|
await this.user.fetchUser();
|
|
|
|
|
EV.publish(EVENTS.userEmailConfirmed);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.log("SSE: Unsupported message. Message = ", event.data);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async lastSynced() {
|
2024-02-05 22:05:55 +05:00
|
|
|
return (await this.kv().read("lastSynced")) || 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setLastSynced(lastSynced: number) {
|
|
|
|
|
return this.kv().write("lastSynced", lastSynced);
|
2023-08-21 13:32:06 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sync(options: SyncOptions) {
|
|
|
|
|
return this.syncer.start(options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
host(hosts: typeof Hosts) {
|
|
|
|
|
if (process.env.NODE_ENV !== "production") {
|
|
|
|
|
Hosts.AUTH_HOST = hosts.AUTH_HOST || Hosts.AUTH_HOST;
|
|
|
|
|
Hosts.API_HOST = hosts.API_HOST || Hosts.API_HOST;
|
|
|
|
|
Hosts.SSE_HOST = hosts.SSE_HOST || Hosts.SSE_HOST;
|
|
|
|
|
Hosts.SUBSCRIPTIONS_HOST =
|
|
|
|
|
hosts.SUBSCRIPTIONS_HOST || Hosts.SUBSCRIPTIONS_HOST;
|
|
|
|
|
Hosts.ISSUES_HOST = hosts.ISSUES_HOST || Hosts.ISSUES_HOST;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
version() {
|
|
|
|
|
return http.get(`${Hosts.API_HOST}/version`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async announcements() {
|
|
|
|
|
let url = `${Hosts.API_HOST}/announcements/active`;
|
|
|
|
|
const user = await this.user.getUser();
|
|
|
|
|
if (user) url += `?userId=${user.id}`;
|
|
|
|
|
return http.get(url);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default Database;
|