Skip to content

Commit 3395e74

Browse files
authored
Merge pull request #211 from internxt/refactor/migrate-users-and-bucket-models
refactor(users-buckets): migrate users and bucket models
2 parents 50037bf + a19d8e6 commit 3395e74

10 files changed

Lines changed: 488 additions & 89 deletions

File tree

lib/core/users/MongoDBUsersRepository.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ type DatabaseUser = {
1313
};
1414
totalUsedSpaceBytes: 0;
1515
maxSpaceBytes: 0;
16-
referralPartner: null;
1716
subscriptionPlan: {
1817
isSubscribed: boolean;
1918
};
@@ -71,36 +70,20 @@ export class MongoDBUsersRepository implements UsersRepository {
7170
}
7271

7372
async create(data: CreateUserData): Promise<BasicUser> {
74-
const user = await new Promise(
75-
(resolve: (newUser: BasicUser) => void, reject) => {
76-
this.userModel.create(data, (err: Error, user: DatabaseUser) => {
77-
if (err) {
78-
reject(err);
79-
} else {
80-
resolve({
81-
id: user.id,
82-
maxSpaceBytes: user.maxSpaceBytes,
83-
uuid: user.uuid,
84-
});
85-
}
86-
});
87-
}
88-
);
73+
const createdUser = await this.userModel.create(data);
8974

9075
// TODO: Change storage-models to insert only, avoiding updates.
9176
await this.userModel.updateOne(
92-
{
93-
_id: user.id,
94-
},
77+
{ _id: createdUser.id },
9578
{
9679
maxSpaceBytes: data.maxSpaceBytes,
9780
activated: data.activated,
9881
}
9982
);
10083

101-
user.maxSpaceBytes = data.maxSpaceBytes;
84+
createdUser.maxSpaceBytes = data.maxSpaceBytes;
10285

103-
return user;
86+
return createdUser;
10487
}
10588

10689
async updateById(id: string, update: any): Promise<User | null> {

lib/core/users/User.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export interface User {
1515
subscriptionPlan?: {
1616
isSubscribed?: boolean;
1717
};
18-
referralPartner?: string | null;
1918
preferences?: {
2019
dnt: boolean;
2120
};

lib/engine.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const express = require('express');
99
const crossorigin = require('cors');
1010
const helmet = require('helmet');
1111
const Config = require('./config');
12-
const Storage = require('storj-service-storage-models');
12+
const DatabaseConnection = require('./models/database');
1313
const { querystring } = require('./server/middleware/query-string');
1414
const { ErrorHandlerFactory: errorHandler } = require('./server/middleware/error-handler');
1515

@@ -87,11 +87,7 @@ Engine.HEALTH_INTERVAL = 30000;
8787
Engine.prototype.start = function (callback) {
8888
log.info('starting the bridge engine');
8989

90-
this.storage = new Storage(
91-
this._config.storage.mongoUrl,
92-
this._config.storage.mongoOpts,
93-
{ logger: log }
94-
);
90+
this.storage = DatabaseConnection.createFromConfig(this._config.storage, log);
9591

9692
const { QUEUE_USERNAME, QUEUE_PASSWORD, QUEUE_HOST } = this._config;
9793

@@ -127,7 +123,7 @@ Engine.prototype.start = function (callback) {
127123

128124
this.mailer = new Mailer(this._config.mailer);
129125
this.contracts = new storj.StorageManager(
130-
new MongoDBStorageAdapter(this.storage),
126+
new MongoDBStorageAdapter(this.storage.connection),
131127
{ disableReaper: true }
132128
);
133129
this.redis = require('redis').createClient(this._config.redis);
@@ -257,7 +253,6 @@ Engine.prototype._configureApp = function () {
257253
routers.forEach(bindRoute);
258254
app.use(unexpectedErrorLogger);
259255
app.use(errorHandler({ logger: log }));
260-
261256
app.use(json());
262257

263258
const profile = this._config.server.public || this._config.server;

lib/models/bucket.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import crypto from "crypto";
2+
import { Schema, Document, Connection, Types } from "mongoose";
3+
import { validate as uuidValidate, version as uuidVersion } from "uuid";
4+
const errors = require("storj-service-error-types");
5+
6+
interface IBucket extends Document {
7+
storage: number;
8+
transfer: number;
9+
status: "Active" | "Inactive";
10+
pubkeys: string[];
11+
user: string;
12+
userId: string;
13+
name: string;
14+
maxFrameSize: number;
15+
created: Date;
16+
publicPermissions: ("PUSH" | "PULL")[];
17+
encryptionKey: string;
18+
}
19+
20+
const BucketSchema = new Schema<IBucket>(
21+
{
22+
storage: { type: Number, default: 0 },
23+
transfer: { type: Number, default: 0 },
24+
status: {
25+
type: String,
26+
enum: ["Active", "Inactive"],
27+
default: "Active",
28+
},
29+
pubkeys: [{ type: String, ref: "PublicKey" }],
30+
user: { type: String, ref: "User" },
31+
userId: {
32+
type: String,
33+
required: true,
34+
validate: {
35+
validator: (value: string) =>
36+
uuidValidate(value) && uuidVersion(value) === 4,
37+
message: "Invalid UUID",
38+
},
39+
ref: "User",
40+
},
41+
name: {
42+
type: String,
43+
default: () => "Bucket-" + crypto.randomBytes(3).toString("hex"),
44+
},
45+
maxFrameSize: { type: Number, default: -1 },
46+
created: { type: Date, default: Date.now },
47+
publicPermissions: {
48+
type: [{ type: String, enum: ["PUSH", "PULL"] }],
49+
default: [],
50+
},
51+
encryptionKey: { type: String, default: "" },
52+
},
53+
{
54+
statics: {
55+
async create(
56+
user: { _id: string; uuid: string },
57+
data: { pubkeys?: string[]; name?: string },
58+
callback: (err: Error | null, bucket?: IBucket) => void,
59+
) {
60+
const Bucket = this;
61+
62+
const bucket = new Bucket({
63+
status: "Active",
64+
pubkeys: data.pubkeys,
65+
user: user._id,
66+
userId: user.uuid,
67+
});
68+
69+
if (data.name) {
70+
bucket.name = data.name;
71+
}
72+
73+
try {
74+
await bucket.save();
75+
76+
const savedBucket = await Bucket.findOne({
77+
_id: bucket._id,
78+
});
79+
if (!savedBucket) {
80+
return callback(
81+
new errors.InternalError(
82+
"Failed to load created bucket",
83+
),
84+
);
85+
}
86+
87+
callback(null, savedBucket);
88+
} catch (err: any) {
89+
if (err.code === 11000) {
90+
return callback(
91+
new errors.ConflictError(
92+
"Name already used by another bucket",
93+
),
94+
);
95+
}
96+
callback(new errors.InternalError(err.message));
97+
}
98+
},
99+
},
100+
},
101+
);
102+
103+
BucketSchema.index({ user: 1 });
104+
BucketSchema.index({ userId: 1 });
105+
BucketSchema.index({ created: 1 });
106+
BucketSchema.index({ user: 1, name: 1 }, { unique: true });
107+
108+
BucketSchema.set("toObject", {
109+
transform: (doc: any, ret: Record<string, any>) => {
110+
delete ret.__v;
111+
delete ret._id;
112+
ret.id = doc._id;
113+
},
114+
});
115+
116+
export = (connection: Connection) =>
117+
connection.model<IBucket>("Bucket", BucketSchema);

lib/models/database.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
const mongoose = require('mongoose');
5+
6+
/**
7+
* MongoDB storage interface
8+
* @constructor
9+
* @param {String} mongoURI
10+
* @param {Object} mongoOptions
11+
* @param {Object} storageOptions
12+
*/
13+
function Database(mongoURI, mongoOptions, storageOptions) {
14+
if (!(this instanceof Database)) {
15+
return new Database(mongoURI, mongoOptions, storageOptions);
16+
}
17+
18+
assert(typeof mongoOptions === 'object', 'Invalid mongo options supplied');
19+
20+
this._uri = mongoURI;
21+
this._options = mongoOptions;
22+
this._log = (storageOptions && storageOptions.logger) || {
23+
info: console.log,
24+
debug: console.log,
25+
error: console.error,
26+
warn: console.warn,
27+
};
28+
29+
this._connect();
30+
}
31+
32+
Database.externalModels = require('storj-service-storage-models').models;
33+
Database.localModels = {
34+
Bucket: require('./bucket'),
35+
User: require('./user'),
36+
};
37+
Database.constants = require('../constants');
38+
39+
/**
40+
* Connects to the database
41+
*/
42+
Database.prototype._connect = function () {
43+
const opts = Object.assign({ ssl: false }, this._options);
44+
45+
if (opts.server) {
46+
this._log.warn(
47+
'Deprecated \'server\' option detected in database configuration. ' +
48+
'This option was removed in MongoDB driver 4.x and will be ignored. ' +
49+
'Please remove it from your configuration.'
50+
);
51+
delete opts.server;
52+
}
53+
54+
this._log.info('opening database connection at %s', this._uri);
55+
56+
this.connection = mongoose.createConnection(this._uri, opts);
57+
58+
this.connection.on('error', (err) => {
59+
this._log.error('database connection error: %s', err.message);
60+
});
61+
62+
this.connection.on('disconnected', () => {
63+
this._log.warn('disconnected from database');
64+
});
65+
66+
this.connection.on('connected', () => {
67+
this._log.info('connected to database');
68+
});
69+
70+
this.models = this._createBoundModels();
71+
};
72+
73+
/**
74+
* Return a dictionary of models bound to this connection
75+
*/
76+
Database.prototype._createBoundModels = function () {
77+
const bound = {};
78+
79+
const allModels = {
80+
...Database.externalModels,
81+
...Database.localModels,
82+
};
83+
84+
for (const model in allModels) {
85+
bound[model] = allModels[model](this.connection);
86+
}
87+
88+
return bound;
89+
};
90+
91+
/**
92+
* Returns a promise that resolves when the connection is ready
93+
*/
94+
Database.prototype.ready = function () {
95+
return new Promise((resolve, reject) => {
96+
if (this.connection.readyState === 1) {
97+
return resolve();
98+
}
99+
this.connection.once('connected', resolve);
100+
this.connection.once('error', reject);
101+
});
102+
};
103+
104+
/**
105+
* Creates a Database instance from a config object
106+
* @param {Object} storageConfig - { mongoUrl, mongoOpts }
107+
* @param {Object} [logger]
108+
*/
109+
Database.createFromConfig = function (storageConfig, logger) {
110+
return new Database(
111+
storageConfig.mongoUrl,
112+
storageConfig.mongoOpts,
113+
logger ? { logger } : {}
114+
);
115+
};
116+
117+
module.exports = Database;

0 commit comments

Comments
 (0)