diff --git a/.gitignore b/.gitignore
index 7aa94704..8ccd73c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
node_modules
.idea
dist/
+spa/assets/object/
diff --git a/api/.env.example b/api/.env.example
index 48a5b555..8db7bb2a 100644
--- a/api/.env.example
+++ b/api/.env.example
@@ -6,3 +6,4 @@ DB_PASS="pw"
DB_DATABASE="cybertown"
JWT_SECRET="devsecret"
NODE_ENV="development"
+ASSETS_DIR="/usr/src/spa/assets"
diff --git a/api/.gitignore b/api/.gitignore
index e69de29b..de4d1f00 100644
--- a/api/.gitignore
+++ b/api/.gitignore
@@ -0,0 +1,2 @@
+dist
+node_modules
diff --git a/api/.prettierrc.json b/api/.prettierrc.json
new file mode 100644
index 00000000..c0c7f5d7
--- /dev/null
+++ b/api/.prettierrc.json
@@ -0,0 +1,9 @@
+{
+ "trailingComma": "all",
+ "endOfLine": "lf",
+ "tabWidth": 2,
+ "printWidth": 100,
+ "useTabs": false,
+ "singleQuote": true,
+ "arrowParens": "avoid"
+}
\ No newline at end of file
diff --git a/api/db/migrations/20230316031442_create_roles.ts b/api/db/migrations/20230316031442_create_roles.ts
new file mode 100644
index 00000000..88c22343
--- /dev/null
+++ b/api/db/migrations/20230316031442_create_roles.ts
@@ -0,0 +1,36 @@
+import { Knex } from 'knex';
+
+const COLLATE = 'utf8mb4_unicode_ci';
+function applyCommon(table: Knex.CreateTableBuilder) {
+ table.collate(COLLATE);
+ table.increments('id').primary();
+ table.timestamps(false, true);
+}
+
+const tableName = 'role';
+
+export async function up(knex: Knex): Promise {
+ if (!(await knex.schema.hasTable(tableName))) {
+ await knex.schema.createTable(tableName, table => {
+ console.log(`Creating ${tableName} table`);
+ applyCommon(table);
+
+ table.boolean('active').notNullable().defaultTo(true);
+
+ table.string('name').notNullable();
+
+ table.integer('required_xp').unsigned().notNullable().defaultTo(0);
+
+ table.integer('income_xp').unsigned().notNullable().defaultTo(0);
+
+ table.integer('income_cc').unsigned().notNullable().defaultTo(0);
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasTable(tableName)) {
+ console.log(`Dropping ${tableName} table`);
+ await knex.schema.dropTable(tableName);
+ }
+}
diff --git a/api/db/migrations/20230316041126_create_role_assignment.ts b/api/db/migrations/20230316041126_create_role_assignment.ts
new file mode 100644
index 00000000..c97bf4d0
--- /dev/null
+++ b/api/db/migrations/20230316041126_create_role_assignment.ts
@@ -0,0 +1,34 @@
+import { Knex } from 'knex';
+
+const COLLATE = 'utf8mb4_unicode_ci';
+function applyCommon(table: Knex.CreateTableBuilder) {
+ table.collate(COLLATE);
+ table.increments('id').primary();
+ table.timestamps(false, true);
+}
+
+const tableName = 'role_assignment';
+
+export async function up(knex: Knex): Promise {
+ if (!(await knex.schema.hasTable(tableName))) {
+ await knex.schema.createTable(tableName, table => {
+ console.log(`Creating ${tableName} table`);
+ applyCommon(table);
+
+ table.integer('member_id').unsigned().notNullable();
+ table.foreign('member_id').references('member.id');
+
+ table.integer('role_id').unsigned().notNullable();
+ table.foreign('role_id').references('role.id');
+
+ table.integer('place_id').unsigned();
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasTable(tableName)) {
+ console.log(`Dropping ${tableName} table`);
+ await knex.schema.dropTable(tableName);
+ }
+}
diff --git a/api/db/migrations/20230409194209_add_messageboard.ts b/api/db/migrations/20230409194209_add_messageboard.ts
new file mode 100644
index 00000000..c89de36a
--- /dev/null
+++ b/api/db/migrations/20230409194209_add_messageboard.ts
@@ -0,0 +1,68 @@
+import { Knex } from 'knex';
+
+const COLLATE = 'utf8mb4_unicode_ci';
+function applyCommon(table: Knex.CreateTableBuilder) {
+ table.collate(COLLATE);
+ table.increments('id').primary();
+ table.timestamps(false, true);
+}
+
+export async function up(knex: Knex): Promise {
+ if (!await knex.schema.hasTable('messageboard')) {
+ await knex.schema.createTable('messageboard', table => {
+ console.log('Creating messageboard table again');
+ applyCommon(table);
+
+ table.integer('place_id', 10)
+ .unsigned()
+ .notNullable();
+ table.foreign('place_id')
+ .references('place.id');
+
+ table.integer('member_id')
+ .unsigned()
+ .notNullable();
+ table.foreign('member_id')
+ .references('member.id');
+
+ table.text('subject')
+ .notNullable();
+
+ table.text('message')
+ .notNullable();
+
+ table.integer('parent_id', 11)
+ .defaultTo(0)
+ .unsigned();
+
+ table.tinyint('reply', 1)
+ .defaultTo(0)
+ .notNullable();
+
+ table.tinyint('status',1)
+ .defaultTo(1)
+ .notNullable();
+ });
+ }
+
+ if (!await knex.schema.hasColumn('place','messageboard_intro')) {
+ console.log('Adding column messageboard_intro to table place');
+ await knex.schema.alterTable('place', table => {
+ table.string('messageboard_intro');
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasTable('messageboard')) {
+ console.log('Dropping messageboard table');
+ await knex.schema.dropTable('messageboard');
+ }
+
+ if (await knex.schema.hasColumn('place','messageboard_intro')) {
+ console.log('Removing column messageboard_intro from table place');
+ await knex.schema.alterTable('place', table => {
+ table.dropColumn('messageboard_intro');
+ });
+ }
+}
diff --git a/api/db/migrations/20230524130944_add_role_fields_to_member.ts b/api/db/migrations/20230524130944_add_role_fields_to_member.ts
new file mode 100644
index 00000000..52570fdd
--- /dev/null
+++ b/api/db/migrations/20230524130944_add_role_fields_to_member.ts
@@ -0,0 +1,21 @@
+import { Knex } from 'knex';
+
+export async function up(knex: Knex): Promise {
+ if (!(await knex.schema.hasColumn('member', 'primary_role_field'))) {
+ console.log(`Adding role fields to member table`);
+ await knex.schema.alterTable('member', table => {
+ table.integer('primary_role_id');
+ table.timestamp('last_weekly_role_credit').notNullable().defaultTo(knex.fn.now());
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn('member', 'primary_role_id')) {
+ console.log(`Dropping role fields from member table`);
+ await knex.schema.alterTable('member', table => {
+ table.dropColumn('primary_role_id');
+ table.dropColumn('last_weekly_role_credit');
+ });
+ }
+}
diff --git a/api/db/migrations/20230529230442_update_messageboard_column_type.ts b/api/db/migrations/20230529230442_update_messageboard_column_type.ts
new file mode 100644
index 00000000..1c007328
--- /dev/null
+++ b/api/db/migrations/20230529230442_update_messageboard_column_type.ts
@@ -0,0 +1,22 @@
+import { Knex } from 'knex';
+
+
+export async function up(knex: Knex): Promise {
+ if(await knex.schema.hasColumn('place', 'messageboard_intro')) {
+ await knex.schema.alterTable('place', function (table) {
+ table.text('messageboard_intro').alter();
+ });
+ console.log('Changing messageboard_intro to text');
+ }
+}
+
+
+export async function down(knex: Knex): Promise {
+ if(await knex.schema.hasColumn('place', 'messageboard_intro')) {
+ await knex.schema.alterTable('place', function (table) {
+ table.string('messageboard_intro', 255).alter();
+ });
+ console.log('Changing messageboard_intro to varchar(255)');
+ }
+}
+
diff --git a/api/db/migrations/20231113234406_change_transaction_reason_type.ts b/api/db/migrations/20231113234406_change_transaction_reason_type.ts
new file mode 100644
index 00000000..b8548f2f
--- /dev/null
+++ b/api/db/migrations/20231113234406_change_transaction_reason_type.ts
@@ -0,0 +1,21 @@
+import { Knex } from 'knex';
+
+export async function up(knex: Knex): Promise {
+ if(await knex.schema.hasColumn('transaction', 'reason')) {
+ await knex.schema.alterTable('transaction', function (table) {
+ table.string('reason', 50).notNullable().alter();
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ await knex.schema.alterTable('transaction', function (table) {
+ table.enu('reason', [
+ 'daily-credit',
+ 'home-purchase',
+ 'item-purchase',
+ 'member-to-member',
+ 'system-to-member',
+ ]).notNullable().alter();
+ });
+}
diff --git a/api/db/migrations/20231231203030_add_inbox_table.ts b/api/db/migrations/20231231203030_add_inbox_table.ts
new file mode 100644
index 00000000..d9da1457
--- /dev/null
+++ b/api/db/migrations/20231231203030_add_inbox_table.ts
@@ -0,0 +1,70 @@
+import { Knex } from 'knex';
+
+const COLLATE = 'utf8mb4_unicode_ci';
+function applyCommon(table: Knex.CreateTableBuilder) {
+ table.collate(COLLATE);
+ table.increments('id').primary();
+ table.timestamps(false, true);
+}
+
+export async function up(knex: Knex): Promise {
+ if (!await knex.schema.hasTable('inbox')) {
+ await knex.schema.createTable('inbox', table => {
+ console.log('Creating inbox table');
+ applyCommon(table);
+
+ table.integer('place_id', 10)
+ .unsigned()
+ .notNullable();
+ table.foreign('place_id')
+ .references('place.id');
+
+ table.integer('member_id')
+ .unsigned()
+ .notNullable();
+ table.foreign('member_id')
+ .references('member.id');
+
+ table.text('subject')
+ .notNullable();
+
+ table.text('message')
+ .notNullable();
+
+ table.integer('parent_id', 11)
+ .defaultTo(0)
+ .unsigned();
+
+ table.tinyint('reply', 1)
+ .defaultTo(0)
+ .notNullable();
+
+ table.tinyint('status', 1)
+ .defaultTo(1)
+ .notNullable();
+ });
+
+ if (!await knex.schema.hasColumn('place', 'inbox_intro')) {
+ console.log('Adding column inbox_intro to table place');
+ await knex.schema.alterTable('place', table => {
+ table.text('inbox_intro');
+ });
+ }
+ }
+}
+
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasTable('inbox')) {
+ console.log('Dropping inbox table');
+ await knex.schema.dropTable('inbox');
+ }
+
+ if (await knex.schema.hasColumn('place','inbox_intro')) {
+ console.log('Removing column inbox_intro from table place');
+ await knex.schema.alterTable('place', table => {
+ table.dropColumn('inbox_intro');
+ });
+ }
+}
+
diff --git a/api/db/migrations/20240102165858_add_ban_table.ts b/api/db/migrations/20240102165858_add_ban_table.ts
new file mode 100644
index 00000000..51c1265b
--- /dev/null
+++ b/api/db/migrations/20240102165858_add_ban_table.ts
@@ -0,0 +1,43 @@
+import {knex, Knex} from 'knex';
+
+const COLLATE = 'utf8mb4_unicode_ci';
+function applyCommon(table: Knex.CreateTableBuilder) {
+ table.collate(COLLATE);
+ table.increments('id').primary();
+ table.timestamps(false, true);
+}
+
+export async function up(knex: Knex): Promise {
+ if (!await knex.schema.hasTable('ban')){
+ await knex.schema.createTable('ban', (table) => {
+ console.log('Creating ban table');
+ applyCommon(table);
+
+ table.integer('status')
+ .defaultTo(1);
+
+ table.integer('ban_member_id')
+ .notNullable();
+
+ table.date('end_date')
+ .notNullable();
+
+ table.enu('type', ['jail', 'full'])
+ .defaultTo('jail')
+ .notNullable();
+
+ table.integer('assigner_member_id')
+ .notNullable();
+
+ table.text('reason').notNullable();
+ });
+ }
+}
+
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasTable('ban')){
+ await knex.schema.dropTable('ban');
+ }
+}
+
diff --git a/api/db/migrations/20240104192042_object_directory.ts b/api/db/migrations/20240104192042_object_directory.ts
new file mode 100644
index 00000000..11979de6
--- /dev/null
+++ b/api/db/migrations/20240104192042_object_directory.ts
@@ -0,0 +1,23 @@
+import { Knex } from 'knex';
+
+export async function up(knex: Knex): Promise {
+ if (!(await knex.schema.hasColumn('object', 'directory'))) {
+ console.log(`Adding directory, price column to object table`);
+ await knex.schema.alterTable('object', table => {
+ table.string('directory');
+ table.integer('price');
+ table.timestamp('mall_expiration').nullable();
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn('object', 'directory')) {
+ console.log(`Dropping directory, price field from object table`);
+ await knex.schema.alterTable('object', table => {
+ table.dropColumn('directory');
+ table.dropColumn('price');
+ table.dropColumn('mall_expiration');
+ });
+ }
+}
diff --git a/api/db/migrations/20240110203223_drop_place_fk_object_instances.ts b/api/db/migrations/20240110203223_drop_place_fk_object_instances.ts
new file mode 100644
index 00000000..dfc1794e
--- /dev/null
+++ b/api/db/migrations/20240110203223_drop_place_fk_object_instances.ts
@@ -0,0 +1,13 @@
+import { Knex } from 'knex';
+
+export async function up(knex: Knex): Promise {
+ return knex.schema.table('object_instance', function (table) {
+ table.dropForeign('place_id');
+ });
+}
+
+export async function down(knex: Knex): Promise {
+ return knex.schema.table('object_instance', function (table) {
+ table.foreign('place_id').references('place.id');
+ });
+}
diff --git a/api/db/migrations/202401170126_member_chatdefault_field.ts b/api/db/migrations/202401170126_member_chatdefault_field.ts
new file mode 100644
index 00000000..3bdcd66a
--- /dev/null
+++ b/api/db/migrations/202401170126_member_chatdefault_field.ts
@@ -0,0 +1,26 @@
+import { Knex } from 'knex';
+
+const columnName = 'chatdefault';
+
+export async function up(knex: Knex): Promise {
+ if (!await knex.schema.hasColumn('member', columnName)) {
+ console.log(`Adding ${columnName} column to member table`);
+ await knex.schema.alterTable('member', table => {
+ table.integer(columnName)
+ .unsigned()
+ .notNullable()
+ .defaultTo(0);
+ });
+ }
+}
+
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn('member', columnName)) {
+ console.log(`Dropping ${columnName} column from member table`);
+ await knex.schema.alterTable('member', table => {
+ table.dropColumn(columnName);
+ });
+ }
+}
+
diff --git a/api/db/migrations/20240207195122_add_avatar_upload_fields.ts b/api/db/migrations/20240207195122_add_avatar_upload_fields.ts
new file mode 100644
index 00000000..6bd581bf
--- /dev/null
+++ b/api/db/migrations/20240207195122_add_avatar_upload_fields.ts
@@ -0,0 +1,23 @@
+import { Knex } from 'knex';
+
+export async function up(knex: Knex): Promise {
+ if (!(await knex.schema.hasColumn('avatar', 'directory'))) {
+ console.log(`Adding upload columns to avatar table`);
+ await knex.schema.alterTable('avatar', table => {
+ table.string('directory');
+ table.string('image');
+ table.integer('member_id');
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn('avatar', 'directory')) {
+ console.log(`Dropping upload columns from avatar table`);
+ await knex.schema.alterTable('avatar', table => {
+ table.dropColumn('directory');
+ table.dropColumn('image');
+ table.dropColumn('member_id');
+ });
+ }
+}
diff --git a/api/db/migrations/20240225025108_object_instance_add_object_detail_fields.ts b/api/db/migrations/20240225025108_object_instance_add_object_detail_fields.ts
new file mode 100644
index 00000000..ff1496c5
--- /dev/null
+++ b/api/db/migrations/20240225025108_object_instance_add_object_detail_fields.ts
@@ -0,0 +1,25 @@
+import { Knex } from 'knex';
+
+export async function up(knex: Knex): Promise {
+ if (!await knex.schema.hasColumn('object_instance', 'object_name')) {
+ console.log('Adding object detail columns to the object_instance table');
+ await knex.schema.alterTable('object_instance', table => {
+ table.text('object_name').notNullable();
+ table.integer('object_price').unsigned().defaultTo(null);
+ table.text('object_buyer').defaultTo(null);
+ });
+ }
+}
+
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn('object_instance', 'object_name')){
+ console.log('Dropping object detail columns from the object_instance table');
+ await knex.schema.alterTable('object_instance', table => {
+ table.dropColumn('object_name');
+ table.dropColumn('object_price');
+ table.dropColumn('object_buyer');
+ });
+ }
+}
+
diff --git a/api/db/migrations/20240226050159_object_add_texture_field.ts b/api/db/migrations/20240226050159_object_add_texture_field.ts
new file mode 100644
index 00000000..17260326
--- /dev/null
+++ b/api/db/migrations/20240226050159_object_add_texture_field.ts
@@ -0,0 +1,24 @@
+import { Knex } from 'knex';
+
+const columnName = 'texture';
+export async function up(knex: Knex): Promise {
+ if (!await knex.schema.hasColumn('object', columnName)) {
+ console.log(`Adding ${columnName} column to the object table`);
+ await knex.schema.alterTable('object', table => {
+ table.string(columnName)
+ .after('image')
+ .defaultTo(null);
+ });
+ }
+}
+
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn('object', columnName)){
+ console.log(`Dropping ${columnName} column from the object table`);
+ await knex.schema.alterTable('object', table => {
+ table.dropColumn(columnName);
+ });
+ }
+}
+
diff --git a/api/db/migrations/20240420101202_create_mall_object_table.ts b/api/db/migrations/20240420101202_create_mall_object_table.ts
new file mode 100644
index 00000000..2f60a342
--- /dev/null
+++ b/api/db/migrations/20240420101202_create_mall_object_table.ts
@@ -0,0 +1,38 @@
+import { Knex } from 'knex';
+const tableName = 'mall_object';
+const COLLATE = 'utf8mb4_unicode_ci';
+function applyCommon(table: Knex.CreateTableBuilder) {
+ table.collate(COLLATE);
+ table.increments('id').primary();
+ table.timestamps(false, true);
+}
+
+export async function up(knex: Knex): Promise {
+ if(!await knex.schema.hasTable(tableName)) {
+ await knex.schema.createTable(tableName, table => {
+ console.log(`Creating ${tableName} table...`);
+ applyCommon(table);
+
+ table.integer('object_id')
+ .unsigned()
+ .notNullable();
+ table.foreign('object_id')
+ .references('object.id');
+
+ table.integer('place_id')
+ .unsigned();
+
+ table.text('position');
+
+ table.text('rotation');
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasTable(tableName)) {
+ console.log(`Dropping ${tableName} table`);
+ await knex.schema.dropTable(tableName);
+ }
+}
+
diff --git a/api/db/migrations/20240528145929_object_add_limit_field.ts b/api/db/migrations/20240528145929_object_add_limit_field.ts
new file mode 100644
index 00000000..36a9b8f1
--- /dev/null
+++ b/api/db/migrations/20240528145929_object_add_limit_field.ts
@@ -0,0 +1,25 @@
+import { Knex } from 'knex';
+
+const columnName = 'limit';
+export async function up(knex: Knex): Promise {
+ if (!await knex.schema.hasColumn('object', columnName)) {
+ console.log(`Adding ${columnName} column to the object table`);
+ await knex.schema.alterTable('object', table => {
+ table.integer(columnName)
+ .unsigned()
+ .after('quantity')
+ .defaultTo(null);
+ });
+ }
+}
+
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn('object', columnName)){
+ console.log(`Dropping ${columnName} column from the object table`);
+ await knex.schema.alterTable('object', table => {
+ table.dropColumn(columnName);
+ });
+ }
+}
+
diff --git a/api/db/migrations/20240728082907_add_member_online_fields.ts b/api/db/migrations/20240728082907_add_member_online_fields.ts
new file mode 100644
index 00000000..73faae4f
--- /dev/null
+++ b/api/db/migrations/20240728082907_add_member_online_fields.ts
@@ -0,0 +1,26 @@
+import { Knex } from 'knex';
+
+
+export async function up(knex: Knex): Promise {
+ if (!await knex.schema.hasColumn('member', 'place_id')) {
+ console.log('Adding member online columns to the member table');
+ await knex.schema.alterTable('member', table => {
+ table.integer('place_id').unsigned().defaultTo(null);
+ table.timestamp('last_activity').defaultTo(knex.fn.now());
+ table.integer('is_3d').unsigned().defaultTo(0);
+ });
+ }
+}
+
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn('member', 'place_id')){
+ console.log('Dropping member online columns from the member table');
+ await knex.schema.alterTable('member', table => {
+ table.dropColumn('place_id');
+ table.dropColumn('last_activity');
+ table.dropColumn('is_3d');
+ });
+ }
+}
+
diff --git a/api/db/scripts/create-dev-users.ts b/api/db/scripts/create-dev-users.ts
new file mode 100644
index 00000000..495ae82d
--- /dev/null
+++ b/api/db/scripts/create-dev-users.ts
@@ -0,0 +1,20 @@
+import { db } from '../../src/db';
+import { MemberRepository } from '../../src/repositories/member/member.repository';
+
+const members = new MemberRepository(db);
+
+(async () => {
+ console.log('Creating admin user');
+ await members.create({
+ admin: true,
+ email: 'admin@admin',
+ password: 'admin',
+ username: 'admin',
+ });
+ console.log('Creating dev user');
+ await members.create({
+ email: 'dev@dev',
+ password: 'dev',
+ username: 'dev',
+ });
+})();
diff --git a/api/db/seed/05-roles.seed.ts b/api/db/seed/05-roles.seed.ts
new file mode 100644
index 00000000..2a077a1b
--- /dev/null
+++ b/api/db/seed/05-roles.seed.ts
@@ -0,0 +1,9 @@
+import { Knex } from 'knex';
+
+const rolesData = require('./../seed_data/roles_data.json');
+
+export async function seed(knex: Knex): Promise {
+ console.log('Seeding role data');
+
+ await knex('role').insert(rolesData);
+}
diff --git a/api/db/seed/06-donor.roles.seed.ts b/api/db/seed/06-donor.roles.seed.ts
new file mode 100644
index 00000000..c08ff1a4
--- /dev/null
+++ b/api/db/seed/06-donor.roles.seed.ts
@@ -0,0 +1,9 @@
+import { Knex } from 'knex';
+
+const donorData = require('./../seed_data/donor_data.json');
+
+export async function seed(knex: Knex): Promise {
+ console.log('Seeding donor data');
+
+ await knex('role').insert(donorData);
+}
diff --git a/api/db/seed/07-avatars.directory.seed.ts b/api/db/seed/07-avatars.directory.seed.ts
new file mode 100644
index 00000000..164414d8
--- /dev/null
+++ b/api/db/seed/07-avatars.directory.seed.ts
@@ -0,0 +1,12 @@
+/* eslint-disable */
+import { Knex } from 'knex';
+
+/** Insert avatar records for each avatar in spa/assets */
+export async function seed(knex: Knex): Promise {
+ console.log('Fixing directory value for exisiting avatars');
+ await knex('avatar')
+ .where('directory', null)
+ .update({
+ directory: knex.ref('id'),
+ });
+}
diff --git a/api/db/seed/07-mall.store.seed.ts b/api/db/seed/07-mall.store.seed.ts
new file mode 100644
index 00000000..1e387839
--- /dev/null
+++ b/api/db/seed/07-mall.store.seed.ts
@@ -0,0 +1,9 @@
+import { Knex } from 'knex';
+
+const storeData = require('./../seed_data/store_data.json');
+
+export async function seed(knex: Knex): Promise {
+ console.log('Seeding store data');
+
+ await knex('place').insert(storeData);
+}
diff --git a/api/db/seed/08-avatars.image.seed.ts b/api/db/seed/08-avatars.image.seed.ts
new file mode 100644
index 00000000..71b0f3b7
--- /dev/null
+++ b/api/db/seed/08-avatars.image.seed.ts
@@ -0,0 +1,13 @@
+/* eslint-disable */
+import { Knex } from 'knex';
+
+/** Insert avatar records for each avatar in spa/assets */
+export async function seed(knex: Knex): Promise {
+ console.log('Fixing image value for exisiting avatars');
+ await knex('avatar')
+ .where('image', null)
+ .where('member_id', null)
+ .update({
+ image: knex.raw("REPLACE(filename, '.wrl', '.png')")
+ });
+}
diff --git a/api/db/seed_data/donor_data.json b/api/db/seed_data/donor_data.json
new file mode 100644
index 00000000..256f0642
--- /dev/null
+++ b/api/db/seed_data/donor_data.json
@@ -0,0 +1,22 @@
+[
+ {
+ "name": "Supporter",
+ "income_xp": 0,
+ "income_cc": 0
+ },
+ {
+ "name": "Advocate",
+ "income_xp": 0,
+ "income_cc": 0
+ },
+ {
+ "name": "Devotee",
+ "income_xp": 0,
+ "income_cc": 0
+ },
+ {
+ "name": "Champion",
+ "income_xp": 0,
+ "income_cc": 0
+ }
+]
\ No newline at end of file
diff --git a/api/db/seed_data/roles_data.json b/api/db/seed_data/roles_data.json
new file mode 100644
index 00000000..85c57282
--- /dev/null
+++ b/api/db/seed_data/roles_data.json
@@ -0,0 +1,367 @@
+[
+ {
+ "name": "Admin",
+ "income_xp": 0,
+ "income_cc": 0
+ },
+ {
+ "name": "Administrative Secretary",
+ "income_xp": 22,
+ "income_cc": 260
+ },
+ {
+ "name": "Ambassador",
+ "income_xp": 18,
+ "income_cc": 250
+ },
+ {
+ "name": "Apprentice builder",
+ "income_xp": 22,
+ "income_cc": 250
+ },
+ {
+ "name": "Bank Cashier",
+ "income_xp": 12,
+ "income_cc": 200
+ },
+ {
+ "name": "Bank Manager",
+ "income_xp": 24,
+ "income_cc": 250
+ },
+ {
+ "name": "Block Deputy",
+ "income_xp": 14,
+ "income_cc": 225
+ },
+ {
+ "name": "Block Leader",
+ "income_xp": 18,
+ "income_cc": 250
+ },
+ {
+ "name": "CERB",
+ "income_xp": 26,
+ "income_cc": 300
+ },
+ {
+ "name": "CERB Chief",
+ "income_xp": 28,
+ "income_cc": 325
+ },
+ {
+ "name": "City Advisor",
+ "income_xp": 40,
+ "income_cc": 420
+ },
+ {
+ "name": "City Architect",
+ "income_xp": 25,
+ "income_cc": 300
+ },
+ {
+ "name": "City Council",
+ "income_xp": 30,
+ "income_cc": 350
+ },
+ {
+ "name": "City Guide",
+ "income_xp": 20,
+ "income_cc": 250
+ },
+ {
+ "name": "City Mayor",
+ "income_xp": 34,
+ "income_cc": 400
+ },
+ {
+ "name": "Club Assistant",
+ "income_xp": 10,
+ "income_cc": 150
+ },
+ {
+ "name": "Club Owner",
+ "income_xp": 14,
+ "income_cc": 200
+ },
+ {
+ "name": "Clubs Chief",
+ "income_xp": 24,
+ "income_cc": 280
+ },
+ {
+ "name": "Clubs Deputy",
+ "income_xp": 16,
+ "income_cc": 250
+ },
+ {
+ "name": "Colony Deputy",
+ "income_xp": 26,
+ "income_cc": 325
+ },
+ {
+ "name": "Colony Leader",
+ "income_xp": 30,
+ "income_cc": 350
+ },
+ {
+ "name": "Colony Secretary",
+ "income_xp": 20,
+ "income_cc": 275
+ },
+ {
+ "name": "Com Graphics",
+ "income_xp": 28,
+ "income_cc": 325
+ },
+ {
+ "name": "Com tech",
+ "income_xp": 30,
+ "income_cc": 350
+ },
+ {
+ "name": "CVN Cartoonist",
+ "income_xp": 16,
+ "income_cc": 235
+ },
+ {
+ "name": "CVN Cy-tographer",
+ "income_xp": 16,
+ "income_cc": 235
+ },
+ {
+ "name": "CVN Cyto. Exec.",
+ "income_xp": 16,
+ "income_cc": 235
+ },
+ {
+ "name": "CVN Deputy",
+ "income_xp": 16,
+ "income_cc": 250
+ },
+ {
+ "name": "CVN Editor",
+ "income_xp": 26,
+ "income_cc": 325
+ },
+ {
+ "name": "CVN Film Critic Exec.",
+ "income_xp": 18,
+ "income_cc": 250
+ },
+ {
+ "name": "CVN Flim Critic",
+ "income_xp": 16,
+ "income_cc": 235
+ },
+ {
+ "name": "CVN Publisher",
+ "income_xp": 14,
+ "income_cc": 235
+ },
+ {
+ "name": "CVN staff",
+ "income_xp": 16,
+ "income_cc": 235
+ },
+ {
+ "name": "Deputy Ambassador",
+ "income_xp": 16,
+ "income_cc": 225
+ },
+ {
+ "name": "Deputy Mayor",
+ "income_xp": 32,
+ "income_cc": 375
+ },
+ {
+ "name": "Deputy Security Chief",
+ "income_xp": 26,
+ "income_cc": 300
+ },
+ {
+ "name": "Deputy Senior City Guide",
+ "income_xp": 20,
+ "income_cc": 250
+ },
+ {
+ "name": "Employment Chief",
+ "income_xp": 24,
+ "income_cc": 280
+ },
+ {
+ "name": "Employment Deputy",
+ "income_xp": 16,
+ "income_cc": 250
+ },
+ {
+ "name": "ePlex Chief",
+ "income_xp": 24,
+ "income_cc": 280
+ },
+ {
+ "name": "ePlex Deputy",
+ "income_xp": 16,
+ "income_cc": 250
+ },
+ {
+ "name": "Flea Market Chief",
+ "income_xp": 24,
+ "income_cc": 280
+ },
+ {
+ "name": "Flea Market Deputy",
+ "income_xp": 16,
+ "income_cc": 225
+ },
+ {
+ "name": "Founder",
+ "income_xp": 50,
+ "income_cc": 500
+ },
+ {
+ "name": "Homebuilder Manager",
+ "income_xp": 22,
+ "income_cc": 225
+ },
+ {
+ "name": "HTML Tech Support",
+ "income_xp": 12,
+ "income_cc": 230
+ },
+ {
+ "name": "Jail Guard",
+ "income_xp": 18,
+ "income_cc": 230
+ },
+ {
+ "name": "Mall Deputy",
+ "income_xp": 16,
+ "income_cc": 250
+ },
+ {
+ "name": "Mall Manager",
+ "income_xp": 24,
+ "income_cc": 280
+ },
+ {
+ "name": "Master World Builder",
+ "income_xp": 24,
+ "income_cc": 300
+ },
+ {
+ "name": "Mayors Secretary",
+ "income_xp": 26,
+ "income_cc": 325
+ },
+ {
+ "name": "Neighborhood Deputy",
+ "income_xp": 20,
+ "income_cc": 275
+ },
+ {
+ "name": "Neighborhood Leader",
+ "income_xp": 24,
+ "income_cc": 300
+ },
+ {
+ "name": "News Editor",
+ "income_xp": 22,
+ "income_cc": 275
+ },
+ {
+ "name": "Outlands Chief",
+ "income_xp": 24,
+ "income_cc": 320
+ },
+ {
+ "name": "Outlands Deputy",
+ "income_xp": 22,
+ "income_cc": 275
+ },
+ {
+ "name": "Place Chief",
+ "income_xp": 22,
+ "income_cc": 260
+ },
+ {
+ "name": "Place Deputy",
+ "income_xp": 12,
+ "income_cc": 225
+ },
+ {
+ "name": "Post Office Manager",
+ "income_xp": 20,
+ "income_cc": 250
+ },
+ {
+ "name": "Security Advisor",
+ "income_xp": 20,
+ "income_cc": 250
+ },
+ {
+ "name": "Security Captain",
+ "income_xp": 26,
+ "income_cc": 300
+ },
+ {
+ "name": "Security Chief",
+ "income_xp": 28,
+ "income_cc": 325
+ },
+ {
+ "name": "Security Commissioner",
+ "income_xp": 30,
+ "income_cc": 350
+ },
+ {
+ "name": "Security Lieutenant",
+ "income_xp": 24,
+ "income_cc": 280
+ },
+ {
+ "name": "Security Officer",
+ "income_xp": 22,
+ "income_cc": 240
+ },
+ {
+ "name": "Security Sergeant",
+ "income_xp": 22,
+ "income_cc": 260
+ },
+ {
+ "name": "Senior City Guide",
+ "income_xp": 24,
+ "income_cc": 300
+ },
+ {
+ "name": "Sponsor IC",
+ "income_xp": 25,
+ "income_cc": 300
+ },
+ {
+ "name": "Sponsor Team Member",
+ "income_xp": 25,
+ "income_cc": 300
+ },
+ {
+ "name": "Suburbs Chief",
+ "income_xp": 24,
+ "income_cc": 260
+ },
+ {
+ "name": "Tech CD",
+ "income_xp": 26,
+ "income_cc": 325
+ },
+ {
+ "name": "VRML Doc",
+ "income_xp": 18,
+ "income_cc": 230
+ },
+ {
+ "name": "World Builder",
+ "income_xp": 22,
+ "income_cc": 275
+ }
+]
diff --git a/api/db/seed_data/store_data.json b/api/db/seed_data/store_data.json
new file mode 100644
index 00000000..39b3a461
--- /dev/null
+++ b/api/db/seed_data/store_data.json
@@ -0,0 +1,218 @@
+[
+ {
+ "name": "Antique Shop",
+ "description": "Antique Shop",
+ "slug": "antiqueshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Appliance Shop",
+ "description": "Appliance Shop",
+ "slug": "applianceshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Aquatics Shop",
+ "description": "Aquatics Shop",
+ "slug": "aquaticsshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Bargain Outlet",
+ "description": "Bargain Outlet",
+ "slug": "bargainoutlet",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Bedroom Showcase",
+ "description": "Bedroom Showcase",
+ "slug": "bedroomshowcase",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Car Dealer",
+ "description": "Car Dealer",
+ "slug": "cardealer",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Carpet Shop",
+ "description": "Carpet Shop",
+ "slug": "carpetshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Collectibles",
+ "description": "collectibles",
+ "slug": "collectibles",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Electronics Store",
+ "description": "Electronics Store",
+ "slug": "electronicsstore",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Fine Art Shop",
+ "description": "Fine Art Shop",
+ "slug": "fineartshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Furniture Store",
+ "description": "Furniture Store",
+ "slug": "furniturestore",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Garden Store",
+ "description": "Garden Store",
+ "slug": "gardenstore",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "General Store",
+ "description": "General Store",
+ "slug": "generalstore",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Gift Shop",
+ "description": "Gift Shop",
+ "slug": "giftshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "HOLDS DEPOT",
+ "description": "HOLDS DEPOT",
+ "slug": "holdsdepot",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Holiday Shop",
+ "description": "Holiday Shop",
+ "slug": "holidayshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Homebuilder",
+ "description": "Homebuilder",
+ "slug": "homebuilder",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Jewelry Store",
+ "description": "Jewelry Store",
+ "slug": "jewelrystore",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Kitchen Store",
+ "description": "Kitchen Store",
+ "slug": "kitchenstore",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Large Item Shop",
+ "description": "Large Item Shop",
+ "slug": "largeitemshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/largeitems/largeitems.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Magical Corner",
+ "description": "Magical Corner",
+ "slug": "magicalcorner",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Novelty Store",
+ "description": "Novelty Store",
+ "slug": "noveltystore",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Pet Shop",
+ "description": "Pet Shop",
+ "slug": "petshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Space Port",
+ "description": "Space Port",
+ "slug": "spaceport",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Toy Store",
+ "description": "Toy Store",
+ "slug": "toystore",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Weapon Displays",
+ "description": "Weapon Displays",
+ "slug": "weapondisplays",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ },
+ {
+ "name": "Wedding Shop",
+ "description": "Wedding Shop",
+ "slug": "weddingshop",
+ "assets_dir": "/shop/",
+ "world_filename": "vrml/shop.wrl",
+ "type": "shop"
+ }
+]
diff --git a/api/package-lock.json b/api/package-lock.json
index 2482d6a0..2e1b8df1 100644
--- a/api/package-lock.json
+++ b/api/package-lock.json
@@ -2039,6 +2039,11 @@
"babel-preset-current-node-syntax": "^1.0.0"
}
},
+ "badwords-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/badwords-list/-/badwords-list-1.0.0.tgz",
+ "integrity": "sha512-oWhaSG67e+HQj3OGHQt2ucP+vAPm1wTbdp2aDHeuh4xlGXBdWwzZ//pfu6swf5gZ8iX0b7JgmSo8BhgybbqszA=="
+ },
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2171,6 +2176,14 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
+ "busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "requires": {
+ "streamsearch": "^1.1.0"
+ }
+ },
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@@ -2513,8 +2526,7 @@
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
- "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
- "dev": true
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"defer-to-connect": {
"version": "1.1.3",
@@ -2592,6 +2604,39 @@
"esutils": "^2.0.2"
}
},
+ "dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "requires": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ }
+ },
+ "domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
+ },
+ "domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "requires": {
+ "domelementtype": "^2.3.0"
+ }
+ },
+ "domutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+ "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+ "requires": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ }
+ },
"dot-prop": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
@@ -2665,6 +2710,11 @@
"ansi-colors": "^4.1.1"
}
},
+ "entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
+ },
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -2732,8 +2782,7 @@
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
},
"eslint": {
"version": "7.32.0",
@@ -3202,6 +3251,14 @@
}
}
},
+ "express-fileupload": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.4.3.tgz",
+ "integrity": "sha512-vRzZo2YELm68DfR/CX8RMXgeK9BTAANxigrKACPjCXFGEzkCt/QWbqaIXP3W61uaX/hLj0CAo3/EVelpSQXkqA==",
+ "requires": {
+ "busboy": "^1.6.0"
+ }
+ },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3556,6 +3613,17 @@
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true
},
+ "htmlparser2": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+ "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+ "requires": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1",
+ "entities": "^4.4.0"
+ }
+ },
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
@@ -3850,6 +3918,11 @@
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true
},
+ "is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
+ },
"is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
@@ -5161,6 +5234,11 @@
}
}
},
+ "nanoid": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
+ },
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5177,6 +5255,14 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz",
"integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="
},
+ "node-cron": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz",
+ "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==",
+ "requires": {
+ "uuid": "8.3.2"
+ }
+ },
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -5427,6 +5513,11 @@
"lines-and-columns": "^1.1.6"
}
},
+ "parse-srcset": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
+ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
+ },
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -5473,8 +5564,7 @@
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
- "dev": true
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"picomatch": {
"version": "2.3.0",
@@ -5548,6 +5638,16 @@
}
}
},
+ "postcss": {
+ "version": "8.4.23",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz",
+ "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
+ "requires": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ }
+ },
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -5858,6 +5958,19 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
+ "sanitize-html": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.10.0.tgz",
+ "integrity": "sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==",
+ "requires": {
+ "deepmerge": "^4.2.2",
+ "escape-string-regexp": "^4.0.0",
+ "htmlparser2": "^8.0.0",
+ "is-plain-object": "^5.0.0",
+ "parse-srcset": "^1.0.2",
+ "postcss": "^8.3.11"
+ }
+ },
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -5993,6 +6106,11 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
+ "source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+ },
"source-map-support": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
@@ -6036,6 +6154,11 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
+ "streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="
+ },
"string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@@ -6484,6 +6607,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
+ "uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+ },
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
diff --git a/api/package.json b/api/package.json
index ea2dbc1d..bad12161 100644
--- a/api/package.json
+++ b/api/package.json
@@ -8,19 +8,23 @@
"build:prod": "tsc --project tsconfig.prod.json",
"db:init": "ts-node -r dotenv/config db/scripts/create-db.ts && npm run db:migrate && npm run db:seed",
"db:migrate": "knex migrate:latest --knexfile src/knexfile.ts",
+ "db:migrate:make": "knex migrate:make --knexfile src/knexfile.ts",
"db:rollback": "knex migrate:rollback --knexfile src/knexfile.ts",
"db:seed": "knex seed:run --knexfile src/knexfile.ts",
"lint": "eslint -c .eslintrc.json src/**/*",
"test": "jest",
- "dev": "nodemon -e ts --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register -r dotenv/config src/api.ts\""
+ "dev": "nodemon -e ts --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register -r dotenv/config src/api.ts\"",
+ "script": "ts-node -r dotenv/config"
},
"author": "",
"license": "ISC",
"dependencies": {
+ "badwords-list": "^1.0.0",
"bcrypt": "^5.0.1",
"body-parser": "^1.19.0",
"dotenv": "^10.0.0",
"express": "^4.17.1",
+ "express-fileupload": "^1.4.3",
"jsonwebtoken": "^8.5.1",
"knex": "^2.0.0",
"lodash": "^4.17.21",
@@ -28,8 +32,10 @@
"morgan": "^1.10.0",
"mysql": "^2.18.1",
"mysql2": "^2.3.3",
+ "node-cron": "^3.0.2",
"nodemailer": "^6.7.2",
"reflect-metadata": "^0.1.13",
+ "sanitize-html": "^2.10.0",
"typedi": "^0.10.0",
"validator": "^13.6.0"
},
diff --git a/api/src/api.ts b/api/src/api.ts
index 808cf487..e80d1b4b 100644
--- a/api/src/api.ts
+++ b/api/src/api.ts
@@ -1,28 +1,35 @@
import 'reflect-metadata';
import * as http from 'http';
import express from 'express';
-import {
- Request,
- Response,
-} from 'express';
+const fileUpload = require('express-fileupload');
+import { Request, Response } from 'express';
import morgan from 'morgan';
import {
+ adminRoutes,
avatarRoutes,
memberRoutes,
messageRoutes,
placeRoutes,
objectInstanceRoutes,
- hoodRoutes,
+ objectRoutes,
colonyRoutes,
+ hoodRoutes,
blockRoutes,
homeRoutes,
+ messageboardRoutes,
+ inboxRoutes,
+ mallRoutes,
+ fleamarketRoutes,
} from './routes';
+require('./cron/cron')();
+
interface HttpException extends Error {
status: number;
}
const app = express();
+app.use(fileUpload());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(morgan('dev'));
@@ -44,11 +51,17 @@ app.use('/api/member', memberRoutes);
app.use('/api/place', placeRoutes);
app.use('/api/message', messageRoutes);
app.use('/api/object_instance', objectInstanceRoutes);
+app.use('/api/object', objectRoutes);
app.use('/api/avatar', avatarRoutes);
app.use('/api/hood', hoodRoutes);
app.use('/api/colony', colonyRoutes);
app.use('/api/block', blockRoutes);
app.use('/api/home', homeRoutes);
+app.use('/api/messageboard', messageboardRoutes);
+app.use('/api/admin', adminRoutes);
+app.use('/api/inbox', inboxRoutes);
+app.use('/api/mall', mallRoutes);
+app.use('/api/fleamarket', fleamarketRoutes);
app.use((request, response, next) => {
const error = new Error('Not found');
@@ -58,7 +71,7 @@ app.use((request, response, next) => {
app.use((error: HttpException, request: Request, response: Response) => {
response.status(error.status || 500);
- response.json({ error: { message: error.message }});
+ response.json({ error: { message: error.message } });
});
const server = http.createServer(app);
diff --git a/api/src/controllers/admin.controller.ts b/api/src/controllers/admin.controller.ts
new file mode 100644
index 00000000..dfb5f206
--- /dev/null
+++ b/api/src/controllers/admin.controller.ts
@@ -0,0 +1,257 @@
+import {Request, Response} from 'express';
+import { Container } from 'typedi';
+
+import { AdminService, MemberService, AvatarService } from '../services';
+
+class AdminController {
+ constructor(
+ private adminService: AdminService,
+ private memberService: MemberService,
+ private avatarService: AvatarService,
+ ) {}
+
+ public async addBan(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (admin) {
+ try {
+ await this.adminService.addBan(
+ request.body.ban_member_id,
+ request.body.time_frame,
+ request.body.type,
+ session.id,
+ request.body.reason,
+ );
+ response.status(200).json({message: 'Ban added successfully'});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ } else {
+ response.status(403).json({message: 'Access Denied'});
+ }
+ }
+
+ public async addDonor(request: Request, response: Response): Promise{
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const accessLevel = await this.memberService.getAccessLevel(session.id);
+ if (accessLevel === 'admin') {
+ try {
+ await this.adminService.addDonor(
+ request.body.member_id,
+ request.body.level,
+ );
+ response.status(200).json({message: 'Donor added successfully'});
+ } catch (e) {
+ console.log(e);
+ response.status(400).json({error: 'Error adding donor'});
+ }
+ } else {
+ response.status(403).json({error: 'Access Denied'});
+ }
+ }
+
+ public async getBanHistory(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (admin) {
+ try {
+ const banHistory = await this.adminService
+ .getBanHistory(Number(request.query.ban_member_id.toString()));
+ response.status(200).json({banHistory});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ } else {
+ response.status(403).json({message: 'Access Denied'});
+ }
+ }
+
+ public async deleteBan(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (admin) {
+ try {
+ const banId = Number(request.body.banId);
+ const reason = request.body.banReason;
+ const deleteBy = await this.memberService.getMemberInfoPublic(session.id);
+ const updateReason = `${reason} (Deleted by ${deleteBy.username})`;
+ await this.adminService.deleteBan(banId, updateReason);
+ response.status(200).json({message: 'Ban deleted successfully'});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ } else {
+ response.status(403).json({message: 'Access Denied'});
+ }
+ }
+
+ public async getDonor(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const accessLevel = await this.memberService.getAccessLevel(session.id);
+ if (accessLevel === 'admin') {
+ const currentLevel = await this
+ .adminService
+ .getDonor(Number(request.query.memberId));
+ response.status(200).json({donorLevel: currentLevel});
+ } else {
+ response.status(403).json({error: 'Access Denied'});
+ }
+ }
+
+ public async searchUsers(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (admin) {
+ try {
+ const results = await this.adminService.searchUsers(
+ request.query.search.toString(),
+ Number.parseInt(request.query.limit.toString()),
+ Number.parseInt(request.query.offset.toString()),
+ );
+ response.status(200).json({results});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ } else {
+ response.status(403).json({message: 'Access Denied'});
+ }
+ }
+
+ public async searchUserChat(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (admin) {
+ try {
+ const results = await this.adminService.searchUserChat(
+ request.query.search.toString(),
+ Number.parseInt(request.query.user.toString()),
+ Number.parseInt(request.query.limit.toString()),
+ Number.parseInt(request.query.offset.toString()),
+ );
+ response.status(200).json({results});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ } else {
+ response.status(403).json({message: 'Access Denied'});
+ }
+ }
+
+ public async avatars(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (admin) {
+ try {
+ const results = await this.adminService.searchAvatars(
+ parseInt(request.query.status.toString()),
+ parseInt(request.query.limit.toString()),
+ parseInt(request.query.offset.toString()),
+ );
+ response.status(200).json({results});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ } else {
+ response.status(403).json({message: 'Access Denied'});
+ }
+ }
+
+ public async avatarApprove(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (!admin) {
+ response.status(403).json({message: 'Access Denied'});
+ return;
+ }
+ try {
+ this.avatarService.approve(
+ parseInt(request.body.id.toString()),
+ );
+ response.status(200).json({'status':'success'});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ }
+ public async avatarReject(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (!admin) {
+ response.status(403).json({message: 'Access Denied'});
+ return;
+ }
+ try {
+ this.avatarService.reject(
+ parseInt(request.body.id.toString()),
+ );
+ response.status(200).json({'status':'success'});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ }
+
+ public async places(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (admin) {
+ try {
+ const results = await this.adminService.searchPlaces(
+ request.query.type.toString(),
+ parseInt(request.query.limit.toString()),
+ parseInt(request.query.offset.toString()),
+ );
+
+ response.status(200).json({results});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ } else {
+ response.status(403).json({message: 'Access Denied'});
+ }
+ }
+
+ public async placesUpdate(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+ const admin = await this.memberService.canAdmin(session.id);
+ if (admin) {
+ try {
+ this.adminService.updatePlaces(
+ parseInt(request.body.id.toString()),
+ request.body.column.toString(),
+ request.body.content.toString(),
+ );
+ response.status(200).json({status: 'success'});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error});
+ }
+ } else {
+ response.status(403).json({message: 'Access Denied'});
+ }
+ }
+}
+
+const adminService = Container.get(AdminService);
+const memberService = Container.get(MemberService);
+const avatarService = Container.get(AvatarService);
+export const adminController = new AdminController(adminService, memberService, avatarService);
diff --git a/api/src/controllers/avatar.controller.ts b/api/src/controllers/avatar.controller.ts
index f52fa3de..810544e0 100644
--- a/api/src/controllers/avatar.controller.ts
+++ b/api/src/controllers/avatar.controller.ts
@@ -1,43 +1,34 @@
import { Request, Response} from 'express';
+import {Container} from 'typedi';
+import validator from 'validator';
+
+import {
+ AvatarService,
+ MemberService
+} from '../services';
-import { db } from '../db';
-interface QueryParams {
- limit: string,
- order: string,
- orderDirection: string,
-}
class AvatarController {
- public static readonly MAX_LIMIT = 1000;
- public static readonly VALID_ORDERS = ['id'];
- public static readonly VALID_ORDER_DIRECTIONS = ['asc', 'desc'];
- constructor() {}
+ constructor(
+ private avatarService: AvatarService,
+ private memberService: MemberService,
+ ) {}
/**
* Returns an ordered list of all avatars.
*/
public async getResults(request: Request, response: Response): Promise {
- const { limit, order, orderDirection }: QueryParams = ( ( request.query));
- const parsedLimit = parseInt( limit);
-
- const queryLimit = (parsedLimit <= AvatarController.MAX_LIMIT)
- ? parsedLimit
- : 10;
-
- const orderBy = AvatarController.VALID_ORDERS.includes(order)
- ? order
- : 'id';
-
- const queryOrderDirection = AvatarController.VALID_ORDER_DIRECTIONS.includes(orderDirection)
- ? orderDirection
- : '';
-
try {
- const avatars = await db.avatar
- .select('id', 'name')
- .orderBy(orderBy, queryOrderDirection)
- .limit(queryLimit);
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const avatars = await this.avatarService.getResults(session.id);
response.status(200).json({ avatars });
} catch (error) {
console.error(error);
@@ -46,5 +37,150 @@ class AvatarController {
});
}
}
+
+ public async add(request, response: Response): Promise {
+ let fileExtension;
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+
+ if (validator.isEmpty(request.body.name)) {
+ response.status(400).json({
+ error: 'Avatar name is required.',
+ });
+ return;
+ }
+
+ if (!request.files) {
+ response.status(400).json({
+ error: 'VRML file is required',
+ });
+ return;
+ }
+
+ if (
+ typeof request.files.wrlFile === 'undefined' ||
+ validator.isEmpty(request.files.wrlFile.name)
+ ) {
+ response.status(400).json({
+ error: 'VRML file is required',
+ });
+ return;
+ }
+ fileExtension = request.files.wrlFile.name.split('.').pop();
+ if (
+ fileExtension !== 'wrl' ||
+ !['application/octet-stream', 'model/vrml', 'x-world/x-vrml', 'application/x-world'].includes(
+ request.files.wrlFile.mimetype,
+ )
+ ) {
+ response.status(400).json({
+ error: 'VRML file must be a .wrl file',
+ });
+ return;
+ }
+
+ if (request.files.wrlFile.size > AvatarService.WRL_FILESIZE_LIMIT) {
+ response.status(400).json({
+ error: 'VRML file must less than 250kb',
+ });
+ return;
+ }
+
+ if (
+ typeof request.files.textureFile !== 'undefined' &&
+ !validator.isEmpty(request.files.textureFile.name)
+ ) {
+ fileExtension = request.files.textureFile.name.split('.').pop();
+ if (
+ !['jpeg', 'jpg'].includes(fileExtension) ||
+ !['image/jpeg', 'image/pjpeg'].includes(request.files.textureFile.mimetype)
+ ) {
+ response.status(400).json({
+ error: 'Texture file must be a .jpeg or .jpg file'
+ });
+ return;
+ }
+ if (request.files.textureFile.size > AvatarService.TEXTURE_FILESIZE_LIMIT) {
+ response.status(400).json({
+ error: 'Texture file must less than 250kb',
+ });
+ return;
+ }
+ }
+
+ if (
+ typeof request.files.imageFile === 'undefined' ||
+ validator.isEmpty(request.files.imageFile.name)
+ ) {
+ response.status(400).json({
+ error: 'Thumbnail file is required.',
+ });
+ return;
+ }
+
+ fileExtension = request.files.imageFile.name.split('.').pop();
+ if (
+ !['jpeg', 'jpg'].includes(fileExtension) ||
+ !['image/jpeg', 'image/pjpeg'].includes(request.files.imageFile.mimetype)
+ ) {
+ response.status(400).json({
+ error: 'Thumbnail file must be a .jpeg or .jpg file',
+ });
+ return;
+ }
+ if (request.files.imageFile.size > AvatarService.IMAGE_FILESIZE_LIMIT) {
+ response.status(400).json({
+ error: 'Thumbnail file must less than 250kb',
+ });
+ return;
+ }
+
+ let gesturesString = "";
+ if (
+ typeof request.body.gestures !== 'undefined'||
+ !validator.isEmpty(request.body.gestures)
+ ) {
+ gesturesString = JSON.stringify(request.body.gestures.split(","));
+ }
+
+ if (
+ typeof request.body.private === 'undefined'||
+ validator.isEmpty(request.body.private)
+ ) {
+ response.status(400).json({
+ error: 'Usage access is required',
+ });
+ return;
+ }
+
+ try {
+ await this.avatarService.create(
+ request.files.wrlFile,
+ request.files.imageFile,
+ request.files.textureFile ?? null,
+ request.body.name,
+ gesturesString,
+ parseInt(request.body.private),
+ session.id,
+ );
+ } catch (e) {
+ response.status(400).json({
+ error: e,
+ });
+ return;
+ }
+
+ response.status(200).json({
+ status: 'success',
+ });
+ }
}
-export const avatarController = new AvatarController();
+const avatarService = Container.get(AvatarService);
+const memberService = Container.get(MemberService);
+export const avatarController = new AvatarController(avatarService, memberService);
diff --git a/api/src/controllers/block.controller.ts b/api/src/controllers/block.controller.ts
index d3320a0a..d797508c 100644
--- a/api/src/controllers/block.controller.ts
+++ b/api/src/controllers/block.controller.ts
@@ -1,27 +1,21 @@
-import { Request, Response} from 'express';
+import { Request, Response } from 'express';
import { Container } from 'typedi';
-import {db} from '../db';
-import { MemberService, BlockService } from '../services';
+import { MemberService, BlockService, HoodService } from '../services';
class BlockController {
-
constructor(
private memberService: MemberService,
private blockService: BlockService,
+ private hoodService: HoodService,
) {}
public async getBlock(request: Request, response: Response): Promise {
const { id } = request.params;
try {
- const [block] = await db.place.where({ 'id': parseInt(id) });
- const [mapLocation] = await db.mapLocation.where({ 'place_id': parseInt(id) });
-
- const [hood] = await db.place.where({ 'id': mapLocation.parent_place_id });
- const [hoodMapLocation] = await db.mapLocation.where({ 'place_id': hood.id });
-
- const [colony] = await db.place.where({ 'id': hoodMapLocation.parent_place_id });
-
+ const block = await this.blockService.find(parseInt(id));
+ const hood = await this.blockService.getHood(parseInt(id));
+ const colony = await this.hoodService.getColony(hood.id);
response.status(200).json({ block: block, hood: hood, colony: colony });
} catch (error) {
console.error(error);
@@ -32,22 +26,60 @@ class BlockController {
public async getLocations(request: Request, response: Response): Promise {
const { id } = request.params;
try {
- const locations = await this.blockService
- .getMapLocationAndPlaces(parseInt(id));
+ const locations = await this.blockService.getMapLocationAndPlaces(parseInt(id));
response.status(200).json({ locations });
} catch (error) {
console.error(error);
response.status(400).json({ error });
}
}
+ public async getAccessInfoByUsername(request: Request, response: Response): Promise {
+ const { id } = request.params;
+ try {
+ const data = await this.blockService.getAccessInfoByUsername(parseInt(id));
+ response.status(200).json({ data });
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({ error });
+ }
+ }
+ public async postAccessInfo(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = request.params;
+ try {
+ const access = await this.blockService.canManageAccess(parseInt(id), session.id);
+ if (!access) {
+ response.status(403).json({error: 'Access Denied'});
+ return;
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ const deputies = request.body.deputies;
+ const owner = request.body.owner;
+ try {
+ await this.blockService.postAccessInfo(parseInt(id), deputies, owner);
+ response.status(200).json({success: true});
+ } catch (error) {
+ console.log(error);
+ }
+ }
+
public async postLocations(request: Request, response: Response): Promise {
const { id } = request.params;
const { apitoken } = request.headers;
try {
- const session = this.memberService.decodeMemberToken( apitoken);
- if (!session || !(await this.memberService.isAdmin(session.id))) {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.blockService.canAdmin(parseInt(id), session.id))) {
response.status(400).json({
error: 'Invalid or missing token.',
});
@@ -55,23 +87,51 @@ class BlockController {
const { availableLocations } = request.body;
- await db.mapLocation
- .update({available: false })
- .where({ parent_place_id: parseInt(id) });
+ await this.blockService.resetMapLocationAvailability(parseInt(id));
+
+ for (const location of availableLocations) {
+ await this.blockService.setMapLocationAvailable(parseInt(id), location);
+ }
- for(const location of availableLocations) {
- await db.mapLocation
- .insert({
- parent_place_id: parseInt(id),
- location: location,
- available: true,
- })
- .onConflict(['parent_place_id','location'])
- .merge(['available']);
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async canAdmin(request: Request, response: Response): Promise {
+ const id = request.params.id;
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.blockService.canAdmin(parseInt(id), session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
}
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
- response.status(200).json({'status': 'success'});
+ public async canManageAccess(request: Request, response: Response): Promise {
+ const { id } = request.params;
+ const { apitoken } = request.headers;
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.blockService.canManageAccess(parseInt(id), session.id))) {
+ response.status(403).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ response.status(200).json({ status: 'success' });
} catch (error) {
console.error(error);
response.status(400).json({ error });
@@ -80,4 +140,5 @@ class BlockController {
}
const memberService = Container.get(MemberService);
const blockService = Container.get(BlockService);
-export const blockController = new BlockController(memberService, blockService);
+const hoodService = Container.get(HoodService);
+export const blockController = new BlockController(memberService, blockService, hoodService);
diff --git a/api/src/controllers/colony.controller.ts b/api/src/controllers/colony.controller.ts
index 843593e3..b6b05bc0 100644
--- a/api/src/controllers/colony.controller.ts
+++ b/api/src/controllers/colony.controller.ts
@@ -1,33 +1,103 @@
-import { Request, Response} from 'express';
-
-import { knex } from '../db';
+import { Request, Response } from 'express';
+import { Container } from 'typedi';
+import { PlaceService, ColonyService, MemberService } from '../services';
class ColonyController {
-
- constructor() {}
+ constructor(
+ private placeService: PlaceService,
+ private colonyService: ColonyService,
+ private memberService: MemberService,
+ ) {}
public async getHoods(request: Request, response: Response): Promise {
const { slug } = request.params;
try {
-
- const hoods = await knex
- .select('place.id',
- 'place.name',
- 'map_location.location',
- )
- .from('place')
- .innerJoin('map_location', 'map_location.place_id', 'place.id')
- .innerJoin('place as colony', 'map_location.parent_place_id', 'colony.id')
- .where('colony.slug', slug)
- .orderBy('map_location.location');
+ const colony = await this.placeService.findBySlug(slug);
+ const hoods = await this.colonyService.getHoods(colony.id);
response.status(200).json({ hoods });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+ public async getAccessInfoByUsername(request: Request, response: Response): Promise {
+ const { id } = request.params;
+ try {
+ const data = await this.colonyService.getAccessInfoByUsername(parseInt(id));
+ response.status(200).json({ data });
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({ error });
+ }
+ }
+ public async postAccessInfo(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = request.params;
+ try {
+ const access = await this.colonyService.canManageAccess(parseInt(id), session.id);
+ if (!access) {
+ response.status(403).json({error: 'Access Denied'});
+ return;
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ const deputies = request.body.deputies;
+ const owner = request.body.owner;
+ try {
+ await this.colonyService.postAccessInfo(parseInt(id), deputies, owner);
+ response.status(200).json({success: true});
+ } catch (error) {
+ console.log(error);
+ }
+ }
+ public async canAdmin(request: Request, response: Response): Promise {
+ const { id } = request.params;
+ const { apitoken } = request.headers;
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.colonyService.canAdmin(parseInt(id), session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ response.status(200).json({ status: 'success' });
} catch (error) {
console.error(error);
response.status(400).json({ error });
}
}
+ public async canManageAccess(request: Request, response: Response): Promise {
+ const { id } = request.params;
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.colonyService.canManageAccess(parseInt(id), session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
}
-export const colonyController = new ColonyController();
+const placeService = Container.get(PlaceService);
+const colonyService = Container.get(ColonyService);
+const memberService = Container.get(MemberService);
+export const colonyController = new ColonyController(placeService, colonyService, memberService);
diff --git a/api/src/controllers/fleamarket.controller.ts b/api/src/controllers/fleamarket.controller.ts
new file mode 100644
index 00000000..e59da650
--- /dev/null
+++ b/api/src/controllers/fleamarket.controller.ts
@@ -0,0 +1,41 @@
+import { Request, Response } from 'express';
+import { Container } from 'typedi';
+
+import {
+ MemberService,
+ FleaMarketService,
+} from '../services';
+
+class FleamarketController {
+ constructor(
+ private memberService: MemberService,
+ private fleaMarketService: FleaMarketService,
+
+ ) {}
+
+ public async canAdmin(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.fleaMarketService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+
+}
+const memberService = Container.get(MemberService);
+const fleamarketService = Container.get(FleaMarketService);
+export const fleamarketController = new FleamarketController(
+ memberService,
+ fleamarketService,
+);
diff --git a/api/src/controllers/home.controller.ts b/api/src/controllers/home.controller.ts
index 42abe9d8..04796743 100644
--- a/api/src/controllers/home.controller.ts
+++ b/api/src/controllers/home.controller.ts
@@ -1,6 +1,7 @@
import { Request, Response } from 'express';
import { Container } from 'typedi';
import validator from 'validator';
+import * as badwords from 'badwords-list';
import {
MemberService,
@@ -48,7 +49,7 @@ class HomeController {
if(homeData) {
const blockData = await this.homeService.getHomeBlock(homeData.id);
- const homeDesignData = await this.homeService.getPlaceHomeDesign(homeData.id);
+ const homeDesignData = await this.homeService.getPlaceHomeDesign(userId, homeData.id);
response.status(200).json({
homeData: homeData,
blockData: blockData,
@@ -106,6 +107,13 @@ class HomeController {
throw new Error('2D house is required');
}
+ const bannedwords = badwords.regex;
+ if(houseName.match(bannedwords) ||
+ houseDescription.match(bannedwords) ||
+ firstName.match(bannedwords) ||
+ lastName.match(bannedwords)){
+ throw new Error('This language can not be used on CTR!');
+ }
// check they don't already have a home
const homeInfo = await this.homeService.getHome(session.id);
@@ -116,15 +124,24 @@ class HomeController {
// check if they have enough for the home
const memberInfo = await this.memberService.getMemberInfo(session.id);
+ const donor = await this.memberService.getDonorLevel(session.id);
+ let donorLevel = null;
+ if(donor){
+ donorLevel = Object.values(donor).toString();
+ }
let purchaseAmount = 0;
if(home3d) {
// check they have enough in their wallet to buy the 3d home
// this is optional (if not null)
- const homeDesignInfo = this.homeService.getHomeDesign(home3d);
- if(homeDesignInfo.price > memberInfo.walletBalance) {
- throw new Error('Not enough funds to purchase house.');
+ const homeDesignInfo = await this.homeService.getHomeDesign(session.id, home3d);
+ if(donorLevel === 'Champion' && home3d === 'championhome'){
+ purchaseAmount = 0;
+ } else {
+ if(homeDesignInfo.price > memberInfo.walletBalance) {
+ throw new Error('Not enough funds to purchase house.');
+ }
+ purchaseAmount = homeDesignInfo.price;
}
- purchaseAmount = homeDesignInfo.price;
}
await this.homeService.createHome(
@@ -216,21 +233,35 @@ class HomeController {
throw new Error('2D house is required');
}
+ const bannedwords = badwords.regex;
+ if(homeName.match(bannedwords)){
+ throw new Error('This language can not be used on CTR!');
+ }
+
// check they already have a home
const homeInfo = await this.homeService.getHome(session.id);
if(!homeInfo) {
throw new Error('You don\'t have a home yet.');
} else {
-
- const currentHomeDesign = await this.homeService.getPlaceHomeDesign(homeInfo.id);
+ const donor = await this.memberService.getDonorLevel(session.id);
+ let donorLevel = null;
+ if(donor){
+ donorLevel = Object.values(donor).toString();
+ }
+ const currentHomeDesign = await this
+ .homeService
+ .getPlaceHomeDesign(session.id, homeInfo.id);
let refund = 0;
let currentHomeDesignId = null;
if(currentHomeDesign) {
- refund = currentHomeDesign.price;
+ if(donorLevel === 'Champion' && currentHomeDesign.id === 'championhome'){
+ refund = 0;
+ } else {
+ refund = currentHomeDesign.price;
+ }
currentHomeDesignId = currentHomeDesign.id;
}
-
// check if they have enough for the home
const memberInfo = await this.memberService.getMemberInfo(session.id);
let purchaseAmount = 0;
@@ -239,16 +270,19 @@ class HomeController {
) {
// check they have enough in their wallet to buy the 3d home
// this is optional (if not null)
- const homeDesignInfo = this.homeService.getHomeDesign(home3d);
+ const homeDesignInfo = await this.homeService.getHomeDesign(session.id, home3d);
if(typeof homeDesignInfo.id === 'undefined') {
throw new Error('Home design not found.');
}
- if(homeDesignInfo.price > (memberInfo.walletBalance + refund)) {
- throw new Error('Not enough funds to purchase house.');
+ if(donorLevel === 'Champion' && home3d === 'championhome'){
+ purchaseAmount = 0;
+ } else {
+ if(homeDesignInfo.price > (memberInfo.walletBalance + refund)) {
+ throw new Error('Not enough funds to purchase house.');
+ }
+ purchaseAmount = homeDesignInfo.price;
}
- purchaseAmount = homeDesignInfo.price;
-
}
await this.homeService.updateHome(
@@ -261,7 +295,7 @@ class HomeController {
if(home3d !== currentHomeDesignId) {
if(refund > 0) {
await this.memberService
- .performHomeRefundTransaction(session.id, currentHomeDesign.price);
+ .performHomeRefundTransaction(session.id, refund);
}
if(purchaseAmount > 0) {
diff --git a/api/src/controllers/hood.controller.ts b/api/src/controllers/hood.controller.ts
index 927952b4..7594ce8a 100644
--- a/api/src/controllers/hood.controller.ts
+++ b/api/src/controllers/hood.controller.ts
@@ -1,17 +1,15 @@
-import { Request, Response} from 'express';
-
-import {db, knex} from '../db';
+import { Request, Response } from 'express';
+import { HoodService, MemberService } from '../services';
+import { Container } from 'typedi';
class HoodController {
-
- constructor() {}
+ constructor(private hoodService: HoodService, private memberService: MemberService) {}
public async getHood(request: Request, response: Response): Promise {
const { id } = request.params;
try {
- const [hood] = await db.place.where({ 'id': parseInt(id) });
- const [mapLocation] = await db.mapLocation.where({ 'place_id': parseInt(id) });
- const [colony] = await db.place.where({ 'id': mapLocation.parent_place_id });
+ const hood = await this.hoodService.find(parseInt(id));
+ const colony = await this.hoodService.getColony(parseInt(id));
response.status(200).json({ hood: hood, colony: colony });
} catch (error) {
@@ -23,24 +21,91 @@ class HoodController {
public async getBlocks(request: Request, response: Response): Promise {
const { id } = request.params;
try {
-
- const blocks = await knex
- .select('place.id',
- 'place.name',
- 'map_location.location',
- )
- .from('place')
- .innerJoin('map_location', 'map_location.place_id', 'place.id')
- .where('map_location.parent_place_id', id)
- .orderBy('map_location.location');
-
+ const blocks = await this.hoodService.getBlocks(parseInt(id));
response.status(200).json({ blocks });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async getAccessInfoByUsername(request: Request, response: Response): Promise {
+ const { id } = request.params;
+ try {
+ const data = await this.hoodService.getAccessInfoByUsername(parseInt(id));
+ response.status(200).json({ data });
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async postAccessInfo(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = request.params;
+ try {
+ const access = await this.hoodService.canManageAccess(parseInt(id), session.id);
+ if (!access) {
+ response.status(403).json({error: 'Access Denied'});
+ return;
+ }
+ } catch (error) {
+ console.log(error);
+ }
+ const deputies = request.body.deputies;
+ const owner = request.body.owner;
+ try {
+ await this.hoodService.postAccessInfo(parseInt(id), deputies, owner);
+ response.status(200).json({success: true});
+ } catch (error) {
+ console.log(error);
+ }
+ }
+
+ public async canAdmin(request: Request, response: Response): Promise {
+ const { id } = request.params;
+ const { apitoken } = request.headers;
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.hoodService.canAdmin(parseInt(id), session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ response.status(200).json({ status: 'success' });
} catch (error) {
console.error(error);
response.status(400).json({ error });
}
}
+ public async canManageAccess(request: Request, response: Response): Promise {
+ const { id } = request.params;
+ const { apitoken } = request.headers;
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.hoodService.canManageAccess(parseInt(id), session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
}
-export const hoodController = new HoodController();
+const hoodService = Container.get(HoodService);
+const memberService = Container.get(MemberService);
+export const hoodController = new HoodController(hoodService, memberService);
diff --git a/api/src/controllers/inbox.controller.ts b/api/src/controllers/inbox.controller.ts
new file mode 100644
index 00000000..02c8783f
--- /dev/null
+++ b/api/src/controllers/inbox.controller.ts
@@ -0,0 +1,302 @@
+import {Request, response, Response} from 'express';
+import validator from 'validator';
+import { Container } from 'typedi';
+import {
+ MemberService,
+ InboxService,
+ ColonyService,
+ HoodService,
+ BlockService,
+ PlaceService,
+ MallService,
+} from '../services';
+import sanitizeHtml from 'sanitize-html';
+
+class InboxController {
+
+ constructor(
+ private memberService: MemberService,
+ private inboxService: InboxService,
+ private colonyService: ColonyService,
+ private hoodService: HoodService,
+ private blockService: BlockService,
+ private placeService: PlaceService,
+ private mallService: MallService,
+ ) {
+ }
+
+ public async adminCheck(placeId, id, type): Promise {
+ if (type === 'colony') {
+ try {
+ return await this.colonyService.canAdmin(placeId, id);
+ } catch (e) {
+ console.log(e);
+ }
+ } else if (type === 'hood') {
+ try {
+ return await this.hoodService.canAdmin(placeId, id);
+ } catch (e) {
+ console.log(e);
+ }
+ } else if (type === 'block') {
+ try {
+ return await this.blockService.canAdmin(placeId, id);
+ } catch (e) {
+ console.log(e);
+ }
+ } else if (type === 'public') {
+ try {
+ const place = await this.placeService.findById(placeId);
+ const access = await this.memberService.getAccessLevel(id);
+ if(place.slug === 'mall' && access === 'none'){
+ return await this.mallService.canAdmin(id);
+ } else {
+ return await this.memberService.canAdmin(id);
+ }
+ } catch (e) {
+ console.log(e);
+ }
+ } else {
+ try {
+ return await this.inboxService.getAdminInfo(placeId, id);
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ }
+ public async getAdminInfo(request: Request, response: Response): Promise {
+ const placeId = Number.parseInt(request.body.place_id);
+ const type = request.body.type;
+ const {apitoken} = request.headers;
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const {id} = session;
+ const admin = await this.adminCheck(placeId, id, type);
+ response.status(200).json({admin});
+ }
+ public async getInfo(request: Request, response: Response): Promise {
+ const placeId = Number.parseInt(request.body.place_id);
+ if (placeId <= 0) {
+ response.status(400).json({
+ error: 'placeId is required.',
+ });
+ return;
+ }
+ try {
+ const placeinfo = await this.inboxService.getInfo(placeId);
+ response.status(200).json({placeinfo});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({
+ error: 'A problem occurred while trying to fetch place information.',
+ });
+ }
+ }
+
+ public async getInboxMessages(request: Request, response: Response): Promise {
+ const placeId = Number.parseInt(request.body.place_id);
+ if (placeId <= 0) {
+ response.status(400).json({
+ error: 'placeId is required.',
+ });
+ return;
+ }
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ err: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = session;
+ if (this.inboxService.getAdminInfo(placeId, id)) {
+ try {
+ const inboxmessages = await this.inboxService.getInboxMessages(placeId);
+ response.status(200).json({inboxmessages});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({
+ error: 'A problem occurred while trying to fetch inbox messages.',
+ });
+ }
+ } else {
+ response.status(403).json({error:'Access Denied'});
+ }
+ }
+
+ public async getMessage(request: Request, response: Response): Promise {
+ const messageId = Number.parseInt(request.body.message_id);
+ const placeId = Number.parseInt(request.body.place_id);
+ if (placeId <= 0) {
+ response.status(400).json({
+ error: 'placeId is required.',
+ });
+ return;
+ }
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ err: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = session;
+ if (this.inboxService.getAdminInfo(placeId, id)) {
+ try {
+ const [getmessage] = await this.inboxService.getMessage(messageId);
+ console.log(getmessage);
+ response.status(200).json(getmessage);
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({
+ err: 'A problems occurred when getting the message',
+ });
+ }
+ }
+ }
+
+ public async postInboxMessage(request: Request, response: Response): Promise {
+ const placeId = Number.parseInt(request.body.place_id);
+ const subject = request.body.subject;
+ const uncleanBody = request.body.body;
+ const cleanBody = await this.inboxService.sanitize(uncleanBody);
+ if (subject === '') {
+ response.status(400).json({
+ error: 'A subject is required',
+ });
+ return;
+ }
+ if (cleanBody === '') {
+ response.status(400).json({
+ error: 'A message is required',
+ });
+ return;
+ }
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ err: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = session;
+ try{
+ const data = await this
+ .inboxService
+ .postInboxMessage(id, placeId, subject, cleanBody);
+ response.status(200).json({data});
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({
+ err: 'An error occurred when trying to post message',
+ });
+ }
+ }
+
+ public async postInboxReply(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = session;
+ const receiverId = Number.parseInt(request.body.memberId);
+ const subject = request.body.subject;
+ const uncleanBody = request.body.body;
+ const cleanBody = await this.inboxService.sanitize(uncleanBody);
+ const parentId = request.body.parent_id;
+ try {
+ const data = await this
+ .inboxService
+ .postInboxReply(id, receiverId, subject, cleanBody, parentId);
+ response.status(200).json({data});
+ } catch (error){
+ console.log(error.error);
+ response.status(400).json({
+ error: `${error}`,
+ });
+ }
+ }
+
+ public async deleteInboxMessage(request: Request, response: Response): Promise {
+ const placeId = Number.parseInt(request.body.place_id);
+ const messageId = Number.parseInt(request.body.message_id);
+ const type = request.body.type;
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = session;
+ const admin = await this.adminCheck(placeId, id, type);
+ if (admin) {
+ try {
+ await this.inboxService.deleteInboxMessage(messageId);
+ response.status(200).json({success: 'deleted'});
+ } catch (error) {
+ console.log(error);
+ }
+ } else {
+ response.status(403).json({error:'Access Denied'});
+ }
+ }
+
+ public async changeInboxIntro(request: Request, response: Response): Promise {
+ const type = request.body.type;
+ const { apitoken } = request.headers;
+ const session = this.memberService.decodeMemberToken( apitoken);
+ if(!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token.',
+ });
+ return;
+ }
+ const { id } = session;
+ const placeId = Number.parseInt(request.body.place_id);
+ const uncleanIntro = request.body.intro;
+ const cleanIntro = await this.inboxService.sanitize(uncleanIntro);
+ const admin = await this.adminCheck(placeId, id, type);
+ if (admin) {
+ try {
+ await this.inboxService.changeInboxIntro(placeId, cleanIntro);
+ response.status(200).json({
+ success: 'intro updated',
+ });
+ } catch (error) {
+ console.log(error);
+ response.status(400).json({error: 'Error on Updating'});
+ }
+ } else {
+ response.status(403).json({error: 'Access Denied'});
+ }
+ }
+}
+const memberService = Container.get(MemberService);
+const inboxService = Container.get(InboxService);
+const colonyServices = Container.get(ColonyService);
+const hoodService = Container.get(HoodService);
+const blockService = Container.get(BlockService);
+const placeService = Container.get(PlaceService);
+const mallService = Container.get(MallService);
+export const inboxController = new InboxController(
+ memberService,
+ inboxService,
+ colonyServices,
+ hoodService,
+ blockService,
+ placeService,
+ mallService);
diff --git a/api/src/controllers/index.ts b/api/src/controllers/index.ts
index 953b0b80..3e74a230 100644
--- a/api/src/controllers/index.ts
+++ b/api/src/controllers/index.ts
@@ -1,9 +1,15 @@
+export * from './admin.controller';
export * from './avatar.controller';
export * from './block.controller';
export * from './colony.controller';
+export * from './fleamarket.controller';
export * from './home.controller';
export * from './hood.controller';
+export * from './mall.controller';
export * from './member.controller';
export * from './message.controller';
export * from './object-instance.controller';
+export * from './object.controller';
export * from './place.controller';
+export * from './messageboard.controller';
+export * from './inbox.controller';
diff --git a/api/src/controllers/mall.controller.ts b/api/src/controllers/mall.controller.ts
new file mode 100644
index 00000000..64da1f35
--- /dev/null
+++ b/api/src/controllers/mall.controller.ts
@@ -0,0 +1,465 @@
+import { Request, Response } from 'express';
+import { Container } from 'typedi';
+
+import {
+ MemberService,
+ MallService,
+ ObjectService,
+ WalletService,
+ ObjectInstanceService,
+} from '../services';
+
+class MallController {
+ constructor(
+ private memberService: MemberService,
+ private mallService: MallService,
+ private objectService: ObjectService,
+ private walletService: WalletService,
+ private objectInstanceService: ObjectInstanceService,
+ ) {}
+
+ public async canAdmin(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async findStores(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+
+ try{
+ const stores = await this.mallService.getMallStores();
+ response.status(200).json({ status: 'success', stores: stores });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+
+ }
+
+ public async findAllObjects(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ const columnValues = ['id', 'member_id', 'name', 'status'];
+ const compareValues = ['=', '!=', '>', '<', '>=', '<='];
+
+ const column = request.query.column.toString();
+ const compare = request.query.compare.toString();
+ const content = request.query.content.toString();
+
+ if(columnValues.includes(column) && compareValues.includes(compare)){
+ const objects = await this.mallService
+ .getAllObjects(
+ column,
+ compare,
+ content,
+ Number(request.query.limit),
+ Number(request.query.offset),
+ );
+ response.status(200).json({ status: 'success', objects: objects });
+ } else {
+ response.status(400).json({ status: 'Failed: Invalid search params'});
+ }
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+
+ }
+
+ public async objectsPendingApproval(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ const objects = await this.objectService.getPendingObjects();
+ const returnObjects = [];
+
+ for (const obj of objects) {
+ const member = await this.memberService.find({ id: obj.member_id });
+ obj.username = member.username;
+ returnObjects.push(obj);
+ }
+ response.status(200).json({ status: 'success', objects: returnObjects });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async approveObject(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+
+ this.objectService.updateStatusApproved(
+ parseInt(request.body.objectId));
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async dropMallObject(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+
+ await this.objectService.updateObjectPlace(
+ parseInt(request.body.objectId),parseInt(request.body.shopId));
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async removeMallObject(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+
+ this.objectService.removeMallObject(
+ parseInt(request.body.objectId));
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async deleteMallObject(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+
+ this.objectService.deleteMallObject(
+ parseInt(request.body.objectId));
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async updateObjectLimit(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+
+ this.objectService.updateObjectLimit(
+ parseInt(request.body.objectId),request.body.limit);
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async updateObjectName(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+
+ this.objectService.updateObjectName(
+ parseInt(request.body.objectId),request.body.name);
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async rejectObject(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+
+ const objectRecord = await this.objectService.findById(parseInt(request.body.id));
+ if (!objectRecord) {
+ response.status(400).json({
+ error: 'Invalid or missing object id.',
+ });
+ return;
+ }
+
+ const sellersFee = await this.objectService.getSellerFee(
+ objectRecord.quantity,
+ objectRecord.price,
+ );
+
+ this.objectService.updateStatusRejected(objectRecord.id);
+
+ this.objectService.performObjectUploadRefundTransaction(objectRecord.member_id, sellersFee);
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async refundUnsoldInstances(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+
+ const objectRecord = await this.objectService.findById(parseInt(request.body.id));
+ if (!objectRecord) {
+ response.status(400).json({
+ error: 'Invalid or missing object id.',
+ });
+ return;
+ }
+
+ const instances = await this.objectInstanceService.countById(objectRecord.id);
+ const unsoldInstances = objectRecord.quantity - instances;
+ const newQuantity = objectRecord.quantity - unsoldInstances;
+
+ const sellersFee = await this.objectService.getSellerFee(
+ unsoldInstances,
+ objectRecord.price,
+ );
+
+ this.objectService.updateObjectQuantity(objectRecord.id, newQuantity);
+
+ this.objectService.performUnsoldObjectRefundTransaction(objectRecord.member_id, sellersFee);
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async objectsForSale(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+ try {
+ const placeId = parseInt(request.params.id);
+ const objects = await this.objectService.getMallForSaleObjects(placeId);
+ response.status(200).json({ status: 'success', objects: objects });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async findByObjectId(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ const object = await this.objectService.findByObjectId(parseInt(request.params.id));
+ response.status(200).json({ status: 'success', object: object });
+ } catch(error){
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async findStore(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ const place = await this.mallService.getStore(parseInt(request.params.id));
+ response.status(200).json({ status: 'success', place: place });
+ } catch(error){
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async findByUsername(request: Request, response: Response): Promise {
+ const { apitoken } = request.headers;
+ try {
+ const session = this.memberService.decodeMemberToken(apitoken);
+ if (!session) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ const object = await this.objectService.findByUsername(request.params.username);
+ response.status(200).json({ status: 'success', object: object });
+ } catch(error){
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+
+ public async updateObjectPosition(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session || !(await this.mallService.canAdmin(session.id))) {
+ response.status(400).json({
+ error: 'Invalid or missing token or access denied.',
+ });
+ return;
+ }
+ try {
+ if (
+ typeof request.body?.position.x === 'undefined' ||
+ typeof request.body?.position.y === 'undefined' ||
+ typeof request.body?.position.z === 'undefined' ||
+ typeof request.body?.rotation.x === 'undefined' ||
+ typeof request.body?.rotation.y === 'undefined' ||
+ typeof request.body?.rotation.z === 'undefined' ||
+ typeof request.body?.rotation.angle === 'undefined'
+ ) {
+ throw new Error('Invalid position or rotation.');
+ }
+
+ const id = Number.parseInt(request.params.id);
+
+ await this.mallService.updateObjectPlacement(
+ id,
+ request.body.position,
+ request.body.rotation,
+ );
+
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error: error.message });
+ }
+ }
+
+ public async buyObject(request: Request, response: Response): Promise {
+ const session = this.memberService.decryptSession(request, response);
+ if (!session) return;
+
+ try {
+ const isForSale = await this.mallService.isObjectAvailable(request.body.id);
+ if (!isForSale) {
+ response.status(400).json({
+ error: 'Object is no longer available.',
+ });
+ return;
+ }
+
+ const object = await this.objectService.findById(request.body.id);
+ const member = await this.memberService.find({ id: session.id });
+ const wallet = await this.walletService.findById(member.wallet_id);
+ if (object.price > wallet.balance) {
+ response.status(400).json({
+ error: 'Not enough funds to buy this object.',
+ });
+ return;
+ }
+
+ await this.objectInstanceService.add(object, session.id);
+ await this.objectService.performObjectPurchaseTransaction(session.id, object.price);
+ await this.objectService.performObjectProfitTransaction(object.member_id, object.price);
+
+ response.status(200).json({ status: 'success' });
+ } catch (error) {
+ console.error(error);
+ response.status(400).json({ error });
+ }
+ }
+}
+const memberService = Container.get(MemberService);
+const mallService = Container.get(MallService);
+const objectService = Container.get(ObjectService);
+const walletService = Container.get(WalletService);
+const objectInstanceService = Container.get(ObjectInstanceService);
+export const mallController = new MallController(
+ memberService,
+ mallService,
+ objectService,
+ walletService,
+ objectInstanceService,
+);
+
diff --git a/api/src/controllers/member.controller.ts b/api/src/controllers/member.controller.ts
index 7041fe8f..f3e5748f 100644
--- a/api/src/controllers/member.controller.ts
+++ b/api/src/controllers/member.controller.ts
@@ -3,16 +3,10 @@ import bcrypt from 'bcrypt';
import { Request, Response } from 'express';
import { Container } from 'typedi';
import validator from 'validator';
+import * as badwords from 'badwords-list';
-import {db, knex} from '../db';
-import {
- sendPasswordResetEmail,
- sendPasswordResetUnknownEmail,
-} from '../libs';
-import {
- MemberService,
- HomeService,
-} from '../services';
+import { sendPasswordResetEmail, sendPasswordResetUnknownEmail } from '../libs';
+import { MemberService, HomeService, PlaceService } from '../services';
import { SessionInfo } from 'session-info.interface';
class MemberController {
@@ -34,10 +28,27 @@ class MemberController {
* @param memberService service for interacting with member models
*/
constructor(
- private memberService: MemberService,
+ private memberService: MemberService,
private homeService: HomeService,
- ) {}
+ private placeService: PlaceService) {}
+ public async getAdminLevel(request: Request, response: Response): Promise
-
+
Reset my password
diff --git a/api/src/repositories/avatar/avatar.repository.ts b/api/src/repositories/avatar/avatar.repository.ts
index f65b4e66..2b384344 100644
--- a/api/src/repositories/avatar/avatar.repository.ts
+++ b/api/src/repositories/avatar/avatar.repository.ts
@@ -6,16 +6,132 @@ import { Avatar } from 'models';
/** Repository for fetching/interacting with avatar data in the database. */
@Service()
export class AvatarRepository {
-
constructor(private db: Db) {}
/**
* Finds an avatar with the given search parameters if one exists.
* @param avatarSearchParams object containing properties of an avatar for searching on
* @returns promise resolving in the found avatar object, or rejecting on error
+ * Finds all avatars
+ * @returns promise resolving in the found avatars object, or rejecting on error
*/
public async find(avatarSearchParams: Partial): Promise {
const [avatar] = await this.db.avatar.where(avatarSearchParams);
return avatar;
}
+
+ /**
+ * Finds all avatars
+ * @returns promise resolving in the found avatars object, or rejecting on error
+ */
+ public async findAll(): Promise {
+ return this.db.avatar.where({ status: 1 });
+ }
+
+ /**
+ * gets all the avatars a memberId can access
+ * @param memberId
+ * @returns
+ */
+ public async findAllForMemberId(memberId): Promise {
+ return this.db.avatar
+ .where({
+ status: 1,
+ })
+ .andWhere(builder => {
+ builder.where({ private: 0 }).orWhere({ private: 1, member_id: memberId });
+ });
+ }
+
+ /**
+ * gets avatar by id a memberId can access
+ * @param avatarId
+ * @param memberId
+ * @returns
+ */
+ public async getByIdAndMemberId(avatarId, memberId): Promise {
+ return this.db.avatar
+ .where({
+ id: avatarId,
+ status: 1,
+ })
+ .andWhere(builder => {
+ builder.where({ private: 0 }).orWhere({ private: 1, member_id: memberId });
+ });
+ }
+
+ public async updateStatus(id, status): Promise {
+ return this.db.avatar
+ .update({
+ status: status,
+ })
+ .where({
+ id: id
+ });
+ }
+
+ /**
+ *
+ * @param directory
+ * @param fileName
+ * @param image
+ * @param name
+ * @param gestures
+ * @param privateStatus
+ * @param memberId
+ * @returns
+ */
+ public async create(
+ directory: string,
+ fileName: string,
+ image: string,
+ name: string,
+ gestures: string,
+ privateStatus: number,
+ memberId: number,
+ status: number,
+ ): Promise {
+ const [avatar] = await this.db.avatar.insert({
+ directory: directory,
+ filename: fileName,
+ image: image,
+ name: name,
+ gestures: gestures,
+ private: privateStatus,
+ member_id: memberId,
+ status: status,
+ });
+
+ return avatar;
+ }
+
+ /**
+ * This is to assist with the pagination of the avatar search
+ * @param status
+ * @return number
+ */
+ public async totalByStatus(status: number): Promise {
+ return this.db.avatar.count('id as count').where({
+ status: status,
+ });
+ }
+
+ /**
+ * returns results of avatars by status (pagination)
+ * @param status
+ * @param limit
+ * @param offset
+ * @returns
+ */
+ public async findByStatus(status: number, limit: number, offset: number): Promise {
+ return this.db.avatar
+ .select(['avatar.*', 'member.username'])
+ .leftJoin('member', 'avatar.member_id', 'member.id')
+ .where({
+ 'avatar.status': status,
+ })
+ .orderBy('avatar.id')
+ .limit(limit)
+ .offset(offset);
+ }
}
diff --git a/api/src/repositories/ban/ban.repository.ts b/api/src/repositories/ban/ban.repository.ts
new file mode 100644
index 00000000..0f0ebbe9
--- /dev/null
+++ b/api/src/repositories/ban/ban.repository.ts
@@ -0,0 +1,62 @@
+import {Service} from 'typedi';
+
+import {Db} from '../../db/db.class';
+import {knex} from '../../db';
+import {Member} from 'models';
+
+@Service()
+export class BanRepository {
+ constructor(
+ private db: Db,
+ ) {
+ }
+
+ public async addBan(ban_member_id, end_date, type, assigner_member_id, reason) {
+ return knex('ban')
+ .insert({
+ ban_member_id: ban_member_id,
+ end_date: end_date,
+ type: type,
+ assigner_member_id: assigner_member_id,
+ reason: reason,
+ });
+ }
+
+ public async deleteBan(banId: number, updateReason: string): Promise {
+ return knex('ban')
+ .where({id: banId})
+ .update({
+ status: 0,
+ reason: updateReason,
+ });
+ }
+
+ public async getBanHistory(ban_member_id: number): Promise {
+ return knex
+ .select(
+ 'ban.id',
+ 'ban.created_at',
+ 'ban.end_date',
+ 'ban.type',
+ 'member.username',
+ 'ban.reason',
+ )
+ .from('ban')
+ .innerJoin('member', 'ban.assigner_member_id', 'member.id')
+ .where('ban.ban_member_id', ban_member_id)
+ .where('ban.status', 1)
+ .orderBy('ban.created_at', 'desc');
+ }
+
+ public async getBanMaxDate(member_id): Promise {
+ return this.db.knex
+ .select('end_date', 'reason', 'type')
+ .from('ban')
+ .where('ban_member_id', member_id)
+ .where('status', 1)
+ .orderBy('end_date', 'desc')
+ .limit(1)
+ .first();
+ }
+
+}
diff --git a/api/src/repositories/block/block.repository.ts b/api/src/repositories/block/block.repository.ts
index 48d118cf..75b0a86e 100644
--- a/api/src/repositories/block/block.repository.ts
+++ b/api/src/repositories/block/block.repository.ts
@@ -1,15 +1,89 @@
-import { Service } from 'typedi';
+import {Service} from 'typedi';
-import { Db } from '../../db/db.class';
-import {knex} from '../../db';
+import {Db} from '../../db/db.class';
+import {Place} from '../../types/models';
@Service()
export class BlockRepository {
-
constructor(private db: Db) {}
+ public async find(blockId: number): Promise {
+ return this.db.place.where({ type: 'block', id: blockId }).first();
+ }
+
+ public async getAccessInfoByUsername(
+ blockId,
+ ownerCode,
+ deputyCode): Promise<{ owner: any[]; deputies: any[] }> {
+ const owner: any[] = await this.db.knex
+ .select(
+ 'member.username',
+ )
+ .from('role_assignment')
+ .where('role_assignment.place_id', blockId)
+ .where('role_assignment.role_id', ownerCode)
+ .innerJoin('member', 'role_assignment.member_id', 'member.id');
+ const deputies: any[] = await this.db.knex
+ .select(
+ 'member.username',
+ )
+ .from('role_assignment')
+ .where('role_assignment.place_id', blockId)
+ .where('role_assignment.role_id', deputyCode)
+ .innerJoin('member', 'role_assignment.member_id', 'member.id');
+ return {deputies, owner};
+ }
+
+ public async getAccessInfoByID(
+ blockId,
+ ownerCode,
+ deputyCode): Promise<{ owner: any[]; deputies: any[] }> {
+ const owner: any[] = await this.db.knex
+ .select(
+ 'member_id',
+ )
+ .from('role_assignment')
+ .where('place_id', blockId)
+ .where('role_id', ownerCode);
+ const deputies: any[] = await this.db.knex
+ .select(
+ 'member_id',
+ )
+ .from('role_assignment')
+ .where('place_id', blockId)
+ .where('role_id', deputyCode);
+ return {deputies, owner};
+ }
+
+ public async addIdToAssignment(
+ blockId: number,
+ memberId: number,
+ roleId: number,
+ ): Promise {
+ return this.db.knex('role_assignment')
+ .insert(
+ {
+ role_id: roleId,
+ member_id: memberId,
+ place_id: blockId,
+ },
+ );
+ }
+
+ public async removeIdFromAssignment(
+ blockId: number,
+ memberId: number,
+ roleId: number,
+ ): Promise {
+ return this.db.knex('role_assignment')
+ .where('place_id', blockId)
+ .where('member_id', memberId)
+ .where('role_id', roleId)
+ .del();
+ }
+
public async getMapLocationAndPlacesByBlockId(blockId: number): Promise {
- const locations = await knex
+ const locations = await this.db.knex
.select(
'map_location.location',
'map_location.available',
@@ -26,5 +100,4 @@ export class BlockRepository {
return locations;
}
-
}
diff --git a/api/src/repositories/colony/colony.repository.ts b/api/src/repositories/colony/colony.repository.ts
new file mode 100644
index 00000000..35f6cb4d
--- /dev/null
+++ b/api/src/repositories/colony/colony.repository.ts
@@ -0,0 +1,95 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import { knex } from '../../db';
+import { Place } from '../../types/models';
+
+@Service()
+export class ColonyRepository {
+ constructor(private db: Db) {}
+
+ public async find(colonyId: number): Promise {
+ return this.db.place.where({ type: 'colony', id: colonyId }).first();
+ }
+
+ public async getAccessInfoByUsername(
+ colonyId,
+ ownerCode,
+ deputyCode): Promise<{ owner: any[]; deputies: any[] }> {
+ const owner: any[] = await this.db.knex
+ .select(
+ 'member.username',
+ )
+ .from('role_assignment')
+ .where('role_assignment.place_id', colonyId)
+ .where('role_assignment.role_id', ownerCode)
+ .innerJoin('member', 'role_assignment.member_id', 'member.id');
+ const deputies: any[] = await this.db.knex
+ .select(
+ 'member.username',
+ )
+ .from('role_assignment')
+ .where('role_assignment.place_id', colonyId)
+ .where('role_assignment.role_id', deputyCode)
+ .innerJoin('member', 'role_assignment.member_id', 'member.id');
+ return {deputies, owner};
+ }
+
+ public async getAccessInfoByID(
+ colonyId,
+ ownerCode,
+ deputyCode): Promise<{ owner: any[]; deputies: any[] }> {
+ const owner: any[] = await this.db.knex
+ .select(
+ 'member_id',
+ )
+ .from('role_assignment')
+ .where('place_id', colonyId)
+ .where('role_id', ownerCode);
+ const deputies: any[] = await this.db.knex
+ .select(
+ 'member_id',
+ )
+ .from('role_assignment')
+ .where('place_id', colonyId)
+ .where('role_id', deputyCode);
+ return {deputies, owner};
+ }
+
+ public async getHoods(colonyId: number): Promise {
+ return this.db.knex
+ .select('place.id', 'place.name', 'map_location.location')
+ .from('place')
+ .innerJoin('map_location', 'map_location.place_id', 'place.id')
+ .innerJoin('place as colony', 'map_location.parent_place_id', 'colony.id')
+ .where('colony.id', colonyId)
+ .orderBy('map_location.location');
+ }
+
+ public async addIdToAssignment(
+ colonyId: number,
+ memberId: number,
+ roleId: number,
+ ): Promise {
+ return this.db.knex('role_assignment')
+ .insert(
+ {
+ role_id: roleId,
+ member_id: memberId,
+ place_id: colonyId,
+ },
+ );
+ }
+
+ public async removeIdFromAssignment(
+ colonyId: number,
+ memberId: number,
+ roleId: number,
+ ): Promise {
+ return this.db.knex('role_assignment')
+ .where('place_id', colonyId)
+ .where('member_id', memberId)
+ .where('role_id', roleId)
+ .del();
+ }
+}
diff --git a/api/src/repositories/home-design/home-design.repository.ts b/api/src/repositories/home-design/home-design.repository.ts
index fe5c5dfa..9319f6ac 100644
--- a/api/src/repositories/home-design/home-design.repository.ts
+++ b/api/src/repositories/home-design/home-design.repository.ts
@@ -51,6 +51,10 @@ export class HomeDesignRepository {
'id': '008',
'price': 100000,
},
+ {
+ 'id': 'championhome',
+ 'price': 100000,
+ },
]
constructor() {}
diff --git a/api/src/repositories/hood/hood.repository.ts b/api/src/repositories/hood/hood.repository.ts
new file mode 100644
index 00000000..5b0b2783
--- /dev/null
+++ b/api/src/repositories/hood/hood.repository.ts
@@ -0,0 +1,94 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import { knex } from '../../db';
+import { Place } from '../../types/models';
+
+@Service()
+export class HoodRepository {
+ constructor(private db: Db) {}
+
+ public async find(hoodId: number): Promise {
+ return this.db.place.where({ type: 'hood', id: hoodId }).first();
+ }
+
+ public async getAccessInfoByUsername(
+ hoodId,
+ ownerCode,
+ deputyCode): Promise<{ owner: any[]; deputies: any[] }> {
+ const owner: any[] = await this.db.knex
+ .select(
+ 'member.username',
+ )
+ .from('role_assignment')
+ .where('role_assignment.place_id', hoodId)
+ .where('role_assignment.role_id', ownerCode)
+ .innerJoin('member', 'role_assignment.member_id', 'member.id');
+ const deputies: any[] = await this.db.knex
+ .select(
+ 'member.username',
+ )
+ .from('role_assignment')
+ .where('role_assignment.place_id', hoodId)
+ .where('role_assignment.role_id', deputyCode)
+ .innerJoin('member', 'role_assignment.member_id', 'member.id');
+ return {deputies, owner};
+ }
+
+ public async getAccessInfoByID(
+ hoodId,
+ ownerCode,
+ deputyCode): Promise<{ owner: any[]; deputies: any[] }> {
+ const owner: any[] = await this.db.knex
+ .select(
+ 'member_id',
+ )
+ .from('role_assignment')
+ .where('place_id', hoodId)
+ .where('role_id', ownerCode);
+ const deputies: any[] = await this.db.knex
+ .select(
+ 'member_id',
+ )
+ .from('role_assignment')
+ .where('place_id', hoodId)
+ .where('role_id', deputyCode);
+ return {deputies, owner};
+ }
+
+ public async addIdToAssignment(
+ hoodId: number,
+ memberId: number,
+ roleId: number,
+ ): Promise {
+ return this.db.knex('role_assignment')
+ .insert(
+ {
+ role_id: roleId,
+ member_id: memberId,
+ place_id: hoodId,
+ },
+ );
+ }
+
+ public async removeIdFromAssignment(
+ hoodId: number,
+ memberId: number,
+ roleId: number,
+ ): Promise {
+ return this.db.knex('role_assignment')
+ .where('place_id', hoodId)
+ .where('member_id', memberId)
+ .where('role_id', roleId)
+ .del();
+ }
+
+ public async getBlocks(hoodId: number): Promise {
+ return knex
+ .select('place.id', 'place.name', 'map_location.location')
+ .from('place')
+ .innerJoin('map_location', 'map_location.place_id', 'place.id')
+ .where('map_location.parent_place_id', hoodId)
+ .orderBy('map_location.location');
+ }
+}
diff --git a/api/src/repositories/inbox/inbox.repository.ts b/api/src/repositories/inbox/inbox.repository.ts
new file mode 100644
index 00000000..44605eaa
--- /dev/null
+++ b/api/src/repositories/inbox/inbox.repository.ts
@@ -0,0 +1,147 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db';
+import {knex} from '../../db';
+import {Member, Inbox, Place} from 'models';
+import {response} from 'express';
+
+@Service()
+export class InboxRepository {
+
+ public async changeInboxIntro(
+ placeId: number,
+ Intro: string,
+ ): Promise {
+ return knex('place')
+ .where('id', placeId)
+ .update({inbox_intro: Intro});
+ }
+ constructor(private db: Db) {}
+
+ public async deleteInboxMessage(
+ messageId: number,
+ ): Promise {
+ return knex('inbox')
+ .where('id', messageId)
+ .update({status: 0});
+ }
+
+ public async getAdminInfo(
+ placeId: number,
+ memberId: number,
+ ): Promise {
+ const admininfo = await knex
+ .select(
+ 'admin',
+ )
+ .from('member')
+ .where('id', memberId);
+ const placeinfo = await knex
+ .select('member_id')
+ .from('place')
+ .where('id', placeId);
+ if (admininfo[0].admin || placeinfo[0].member_id === memberId) {
+ const admin = 1;
+ return admin;
+ } else {
+ const admin = 0;
+ return admin;
+ }
+ }
+
+ public async getHomeId(
+ memberId: number,
+ ): Promise {
+ return knex
+ .select('id')
+ .from('place')
+ .where('member_id', memberId);
+ }
+
+ public async getInfo(
+ placeId: number,
+ ): Promise {
+ return knex
+ .select(
+ 'place.inbox_intro as inbox_intro',
+ 'place.name as name',
+ 'place.type as type',
+ )
+ .from('place')
+ .where('place.id', placeId);
+ }
+
+ public async getInboxMessages(
+ placeId: number,
+ ): Promise {
+ return knex
+ .select(
+ 'inbox.created_at',
+ 'inbox.id',
+ 'inbox.reply',
+ 'inbox.subject',
+ 'member.username',
+ 'inbox.parent_id',
+ )
+ .from('inbox')
+ .where('inbox.place_id', placeId)
+ .where('inbox.status', 1)
+ .innerJoin('member', 'inbox.member_id', 'member.id')
+ .orderBy('inbox.parent_id', 'desc')
+ .orderBy('inbox.id', 'asc');
+ }
+
+ public async getMessage(
+ messageId: number,
+ ): Promise {
+ return knex
+ .select(
+ 'message',
+ 'member_id',
+ )
+ .from('inbox')
+ .where('id', messageId);
+ }
+
+ public async postInboxMessage(
+ memberId: number,
+ placeId: number,
+ subject: string,
+ message: string,
+ ): Promise {
+ const parentId = await knex('inbox')
+ .insert(
+ {
+ member_id: memberId,
+ place_id: placeId,
+ subject: subject,
+ message: message,
+ parent_id: 0,
+ },
+ ['id'],
+ );
+ return knex('inbox')
+ .where('id', '=', parentId)
+ .update({'parent_id': parentId});
+ }
+
+ public async postInboxReply(
+ memberId: number,
+ placeId: number,
+ subject: string,
+ message: string,
+ parentId: number,
+ ): Promise {
+ return knex('inbox')
+ .insert(
+ {
+ member_id: memberId,
+ place_id: placeId,
+ subject: subject,
+ message: message,
+ parent_id: parentId,
+ reply: 1,
+ },
+ );
+ }
+}
diff --git a/api/src/repositories/index.ts b/api/src/repositories/index.ts
index 3e15d9be..cfd6a3af 100644
--- a/api/src/repositories/index.ts
+++ b/api/src/repositories/index.ts
@@ -1,9 +1,20 @@
export * from './avatar/avatar.repository';
+export * from './ban/ban.repository';
export * from './block/block.repository';
+export * from './colony/colony.repository';
export * from './home/home.repository';
export * from './home-design/home-design.repository';
+export * from './hood/hood.repository';
+export * from './mall-object/mall-object.repository';
export * from './map-location/map-location.repository';
export * from './member/member.repository';
+export * from './message/message.repository';
+export * from './object/object.repository';
+export * from './object-instance/object-instance.repository';
+export * from './role/role.repository';
+export * from './role-assignment/role-assignment.repository';
export * from './place/place.repository';
export * from './transaction/transaction.repository';
export * from './wallet/wallet.repository';
+export * from './messageboard/messageboard.repository';
+export * from './inbox/inbox.repository';
diff --git a/api/src/repositories/mall-object/mall-object.repository.ts b/api/src/repositories/mall-object/mall-object.repository.ts
new file mode 100644
index 00000000..d9abd970
--- /dev/null
+++ b/api/src/repositories/mall-object/mall-object.repository.ts
@@ -0,0 +1,61 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import { MallObject } from '../../types/models';
+
+/** Repository for fetching/interacting with mall data in the database. */
+@Service()
+export class MallRepository {
+
+ constructor(private db: Db) {}
+
+ public async addToMallObjects(objectId: number): Promise {
+ await this.db.mallObject.insert({object_id: objectId});
+ }
+
+ public async getMallForSale(
+ placeId: number): Promise {
+ const objects = await this.db.mallObject
+ .select('object.*', 'mall_object.place_id', 'mall_object.position', 'mall_object.rotation')
+ .where('place_id', placeId)
+ .where('object.status', 1)
+ .join('object', 'object.id', 'mall_object.object_id')
+ .join('place', 'place.id', 'mall_object.place_id');
+ return objects;
+ }
+
+ public async getStore(objectId: number): Promise {
+ const place = await this.db.mallObject
+ .select('place.*')
+ .where('mall_object.object_id', objectId)
+ .join('place', 'place.id', 'mall_object.place_id');
+ return place;
+ }
+
+ public async findByObjectId(objectId: number): Promise {
+ const object = await this.db.mallObject.where({object_id: objectId});
+ return object;
+ }
+
+ public async updateObjectPlace(
+ mallObjectId: number,
+ shopId: number,
+ ): Promise {
+ await this.db.mallObject.where({ object_id: mallObjectId }).update({
+ place_id: shopId,
+ position: '{"x":0.0,"y":1.75,"z":0.0}',
+ rotation: '{"x":0,"y":0,"z":0,"angle":0}',
+ });
+ }
+
+ public async updateObjectPlacement(
+ mallObjectId: number,
+ positionStr: string,
+ rotationStr: string,
+ ): Promise {
+ await this.db.mallObject.where({ object_id: mallObjectId }).update({
+ position: positionStr,
+ rotation: rotationStr,
+ });
+ }
+}
diff --git a/api/src/repositories/map-location/map-location.repository.ts b/api/src/repositories/map-location/map-location.repository.ts
index 7a006843..0ad39b0c 100644
--- a/api/src/repositories/map-location/map-location.repository.ts
+++ b/api/src/repositories/map-location/map-location.repository.ts
@@ -39,4 +39,23 @@ export class MapLocationRepository {
.where({parent_place_id: parentPlaceId, location: location});
}
+ public async resetAvailabilityByParentPlaceId(parentPlaceId: number): Promise {
+ await this.db.mapLocation
+ .update({available: false })
+ .where({ parent_place_id: parentPlaceId });
+
+ }
+
+ public async createAvailableLocation(parentPlaceId: number , location: number): Promise {
+ await this.db.mapLocation
+ .insert({
+ parent_place_id: parentPlaceId,
+ location: location,
+ available: true,
+ })
+ .onConflict(['parent_place_id','location'])
+ .merge(['available']);
+ }
+
+
}
diff --git a/api/src/repositories/member/member.repository.ts b/api/src/repositories/member/member.repository.ts
index ff55c621..b96c0c28 100644
--- a/api/src/repositories/member/member.repository.ts
+++ b/api/src/repositories/member/member.repository.ts
@@ -1,17 +1,13 @@
-import { Service } from 'typedi';
-
+import {Service} from 'typedi';
import { Db } from '../../db/db.class';
-import {
- Member,
- Wallet,
-} from 'models';
+import { Member, Wallet } from 'models';
+import {knex} from '../../db';
/** Repository for interacting with member table data in the database. */
@Service()
export class MemberRepository {
-
constructor(private db: Db) {}
-
+
/**
* Creates a new member with the given parameters.
* @param memberParams parameters to be used for the new member
@@ -25,7 +21,7 @@ export class MemberRepository {
wallet_id: walletId,
});
return memberId;
- });
+ });
}
/**
@@ -46,6 +42,27 @@ export class MemberRepository {
public async findById(memberId: number): Promise {
return this.find({ id: memberId });
}
+
+ public async findIdByUsername(username: string): Promise {
+ return this.db.knex
+ .select('id')
+ .from('member')
+ .where('username', username);
+ }
+
+ public async check3d(username: string): Promise {
+ return this.db.knex
+ .select('is_3d')
+ .from('member')
+ .where('username', username);
+ }
+
+ public async getActivePlaces(current: Date): Promise {
+ return this.db.knex
+ .select('place_id')
+ .from('member')
+ .where('last_activity','>=', current);
+ }
/**
* Finds a member with the given password reset token if one exists.
@@ -59,6 +76,58 @@ export class MemberRepository {
.limit(1)
.first();
}
+
+ public async getPrimaryRoleName(memberId: number): Promise {
+ return this.db.knex
+ .select('role.name', 'member.primary_role_id')
+ .from('member')
+ .where('member.id', memberId)
+ .join('role', 'member.primary_role_id', 'role.id');
+ }
+
+ /**
+ * This is to assist with the pagination of the user search
+ * @param search
+ * @return number
+ */
+ public async getTotal(search: string): Promise {
+ return knex
+ .count('id as count')
+ .from('member')
+ .where(this.like('username', search));
+ }
+
+ public async countByPlaceId(placeId: number, active: Date): Promise {
+ return knex
+ .count('id as count')
+ .from('member')
+ .where('place_id', placeId)
+ .where('last_activity', '>=', active);
+ }
+
+ public async searchUsers(search: string, limit: number, offset: number): Promise {
+ return knex
+ .select(
+ 'id',
+ 'username',
+ 'email',
+ 'last_daily_login_credit',
+ )
+ .from('member')
+ .where(this.like('username', search))
+ .orWhere(this.like('email', search))
+ .orderBy('id')
+ .limit(limit)
+ .offset(offset);
+ }
+
+ public async joinedPlace(memberId: number, props: Partial): Promise {
+ await this.db.member.where({id: memberId}).update(props);
+ }
+
+ public async updateLatestActivity(memberId: number, props: Partial): Promise {
+ await this.db.member.where({id: memberId}).update(props);
+ }
/**
* Updates properties on the member record with the given id.
@@ -67,13 +136,26 @@ export class MemberRepository {
* @param returning optional. defaults to false. returns the updated record if true.
* @returns promise resolving in the updated member object, or rejecting on error
*/
- public async update(memberId: number, props: Partial, returning = false):
- Promise {
- await this.db.member
- .where({ id: memberId })
- .update(props);
- return returning
- ? this.findById(memberId)
- : undefined;
+ public async update(
+ memberId: number,
+ props: Partial,
+ returning = false,
+ ): Promise {
+ await this.db.member.where({ id: memberId }).update(props);
+ return returning ? this.findById(memberId) : undefined;
+ }
+
+ /**
+ * This is used to bind the user inputted value to prevent
+ * SQL injection attempts while using a Knex Raw
+ * @param field
+ * @param value
+ * @private
+ */
+ private like(field: string, value: string) {
+ return function() {
+ this.whereRaw('?? LIKE ?', [field, `%${value}%`]);
+ };
}
+
}
diff --git a/api/src/repositories/message/message.repository.ts b/api/src/repositories/message/message.repository.ts
new file mode 100644
index 00000000..e10b3419
--- /dev/null
+++ b/api/src/repositories/message/message.repository.ts
@@ -0,0 +1,102 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import {knex} from '../../db';
+import {Message} from '../../types/models';
+
+@Service()
+export class MessageRepository {
+
+ constructor(private db: Db) {}
+
+ public async create(
+ memberId: number,
+ placeId: number,
+ messageBody: string,
+ status: number,
+ ): Promise {
+ const [message] = await this.db.message
+ .insert({
+ body: messageBody,
+ member_id: memberId,
+ place_id: placeId,
+ status: status,
+ });
+
+ return message;
+ }
+
+ public async deleteMessage(id: number): Promise {
+ return knex('message')
+ .where('id', id)
+ .update({
+ status: 0,
+ });
+ }
+
+ public async getChatTotal(search: string, user: number): Promise {
+ return knex
+ .count('message.id as count')
+ .from('message')
+ .innerJoin('place', 'message.place_id', 'place.id')
+ .where('message.member_id', user)
+ .where(this.like('place.name', search));
+ }
+
+ public async getResults(
+ placeId: number,
+ orderField: string,
+ orderDirection: string,
+ limit:number,
+ ): Promise {
+ return knex
+ .select(
+ 'message.id',
+ 'message.body as msg',
+ 'member.username as username',
+ )
+ .from('message')
+ .where('message.place_id', placeId)
+ .where('message.status', '1')
+ .innerJoin('member', 'message.member_id', 'member.id')
+ .orderBy(orderField, orderDirection)
+ .limit(limit);
+ }
+
+ public async searchUserChat(
+ search: string,
+ user: number,
+ limit: number,
+ offset: number,
+ ): Promise {
+ return knex
+ .select(
+ 'message.id',
+ 'message.body',
+ 'message.created_at',
+ 'message.status',
+ 'place.name',
+ )
+ .from('message')
+ .innerJoin('place', 'message.place_id', 'place.id')
+ .where('message.member_id', user)
+ .where(this.like('place.name', search))
+ .orderBy('message.created_at', 'desc')
+ .limit(limit)
+ .offset(offset);
+ }
+
+ /**
+ * This is used to bind the user inputted value to prevent
+ * SQL injection attempts while using a Knex Raw
+ * @param field
+ * @param value
+ * @private
+ */
+ private like(field: string, value: string) {
+ return function() {
+ this.whereRaw('?? LIKE ?', [field, `%${value}%`]);
+ };
+ }
+
+}
diff --git a/api/src/repositories/messageboard/messageboard.repository.ts b/api/src/repositories/messageboard/messageboard.repository.ts
new file mode 100644
index 00000000..6bdc83cb
--- /dev/null
+++ b/api/src/repositories/messageboard/messageboard.repository.ts
@@ -0,0 +1,136 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db';
+import {knex} from '../../db';
+import {Member, MessageBoard, Place} from 'models';
+import {response} from 'express';
+
+@Service()
+export class MessageboardRepository {
+
+ public async changeMessageboardIntro(
+ placeId: number,
+ Intro: string,
+ ): Promise {
+ return knex('place')
+ .where('id', placeId)
+ .update({messageboard_intro: Intro});
+ }
+ constructor(private db: Db) {}
+
+ public async deleteMessageboardMessage(
+ messageId: number,
+ ): Promise {
+ return knex('messageboard')
+ .where('id', messageId)
+ .update({status: 0});
+ }
+
+ public async getAdminInfo(
+ placeId: number,
+ memberId: number,
+ ): Promise {
+ const admininfo = await knex
+ .select(
+ 'admin',
+ )
+ .from('member')
+ .where('id', memberId);
+ const placeinfo = await knex
+ .select('member_id')
+ .from('place')
+ .where('id', placeId);
+ if (admininfo[0].admin || placeinfo[0].member_id === memberId) {
+ const admin = 1;
+ return admin;
+ } else {
+ const admin = 0;
+ return admin;
+ }
+ }
+ public async getInfo(
+ placeId: number,
+ ): Promise {
+ return knex
+ .select(
+ 'place.messageboard_intro as messageboard_intro',
+ 'place.name as name',
+ 'place.type as type',
+ )
+ .from('place')
+ .where('place.id', placeId);
+ }
+
+ public async getMessageboardMessages(
+ placeId: number,
+ ): Promise {
+ return knex
+ .select(
+ 'messageboard.created_at',
+ 'messageboard.id',
+ 'messageboard.reply',
+ 'messageboard.subject',
+ 'member.username',
+ 'messageboard.parent_id',
+ )
+ .from('messageboard')
+ .where('messageboard.place_id', placeId)
+ .where('messageboard.status', 1)
+ .innerJoin('member', 'messageboard.member_id', 'member.id')
+ .orderBy('messageboard.parent_id', 'desc')
+ .orderBy('messageboard.id', 'asc');
+ }
+
+ public async getMessage(
+ messageId: number,
+ ): Promise {
+ return knex
+ .select(
+ 'message',
+ )
+ .from('messageboard')
+ .where('id', messageId);
+ }
+
+ public async postMessageboardMessage(
+ memberId: number,
+ placeId: number,
+ subject: string,
+ message: string,
+ ): Promise {
+ const parentId = await knex('messageboard')
+ .insert(
+ {
+ member_id: memberId,
+ place_id: placeId,
+ subject: subject,
+ message: message,
+ parent_id: 0,
+ },
+ ['id'],
+ );
+ return knex('messageboard')
+ .where('id', '=', parentId)
+ .update({'parent_id': parentId});
+ }
+
+ public async postMessageboardReply(
+ memberId: number,
+ placeId: number,
+ subject: string,
+ message: string,
+ parentId: number,
+ ): Promise {
+ return knex('messageboard')
+ .insert(
+ {
+ member_id: memberId,
+ place_id: placeId,
+ subject: subject,
+ message: message,
+ parent_id: parentId,
+ reply: 1,
+ },
+ );
+ }
+}
diff --git a/api/src/repositories/object-instance/object-instance.repository.ts b/api/src/repositories/object-instance/object-instance.repository.ts
new file mode 100644
index 00000000..c98c86b7
--- /dev/null
+++ b/api/src/repositories/object-instance/object-instance.repository.ts
@@ -0,0 +1,122 @@
+import { Service } from 'typedi';
+import {knex} from '../../db';
+import { Db } from '../../db/db.class';
+import { ObjectInstance, Object } from 'models';
+
+@Service()
+export class ObjectInstanceRepository {
+ constructor(private db: Db) {}
+
+ public async find(objectInstanceId: number): Promise {
+ const [objectInstance] = await this.db.objectInstance.where({
+ id: objectInstanceId,
+ });
+ return objectInstance;
+ }
+
+ public async create(
+ objectId: number, objectName: string, memberId: number, placeId: number): Promise {
+ const [objectInstance] = await this.db.objectInstance.insert({
+ object_id: objectId,
+ object_name: objectName,
+ member_id: memberId,
+ place_id: placeId,
+ });
+ return objectInstance;
+ }
+
+ public async findByPlaceId(placeId: number): Promise {
+ return this.db.objectInstance
+ .select('object_instance.*', 'object.filename', 'object.directory', 'object.name')
+ .where({ place_id: placeId })
+ .join('object', 'object.id', 'object_instance.object_id')
+ .orderBy('object_instance.object_name', 'asc');
+ }
+
+ public async getObjectInstanceWithObject(objectInstanceId: number): Promise {
+ return this.db.objectInstance
+ .select(
+ 'object_instance.*',
+ 'object.filename',
+ 'object.image',
+ 'object.directory',
+ 'object.name',
+ 'member.username')
+ .where('object_instance.id', objectInstanceId)
+ .join('object', 'object.id', 'object_instance.object_id')
+ .join('member', 'member.id', 'object_instance.member_id' );
+ }
+
+ public async updateObjectPlaceId(objectInstanceId: number, placeId: number): Promise {
+ await this.db.objectInstance.where({ id: objectInstanceId }).update({
+ place_id: placeId,
+ });
+ }
+
+ public async updateObjectPlacement(
+ objectInstanceId: number,
+ positionStr: string,
+ rotationStr: string,
+ ): Promise {
+ await this.db.objectInstance.where({ id: objectInstanceId }).update({
+ position: positionStr,
+ rotation: rotationStr,
+ });
+ }
+
+ public async updateObjectInstanceOwner(
+ objectId: number,
+ buyerId: number,
+ ): Promise {
+ return knex('object_instance')
+ .where('id', objectId)
+ .update({
+ member_id: buyerId,
+ place_id: '0',
+ object_price: null,
+ object_buyer: null});
+ }
+
+ public async updateObjectInstanceName(
+ objectId: number,
+ objectName: string,
+ ): Promise {
+ return knex('object_instance')
+ .where('id', objectId)
+ .update({object_name: objectName});
+ }
+
+ public async updateObjectInstancePrice(
+ objectId: number,
+ objectPrice: string,
+ ): Promise {
+ return knex('object_instance')
+ .where('id', objectId)
+ .update({object_price: objectPrice});
+ }
+
+ public async updateObjectInstanceBuyer(
+ objectId: number,
+ objectBuyer: string,
+ ): Promise {
+ return knex('object_instance')
+ .where('id', objectId)
+ .update({object_buyer: objectBuyer});
+ }
+
+ public async countByObjectId(objectId: number): Promise {
+ const count = await this.db.objectInstance
+ .count('object_id as total')
+ .where('object_id', objectId);
+ return parseInt(Object.values(count[0])[0]);
+ }
+
+ public async getMemberBackpack(memberId: number): Promise {
+ return await this.db.objectInstance
+ .select('object_instance.*', 'object.filename', 'object.directory', 'object.name')
+ .join('object', 'object_instance.object_id', 'object.id')
+ .where('object_instance.member_id', memberId)
+ .where('place_id', 0)
+ .orderBy('object_instance.object_name', 'asc');
+ }
+}
diff --git a/api/src/repositories/object/object.repository.ts b/api/src/repositories/object/object.repository.ts
new file mode 100644
index 00000000..75904eed
--- /dev/null
+++ b/api/src/repositories/object/object.repository.ts
@@ -0,0 +1,120 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import { Object } from 'models';
+
+@Service()
+export class ObjectRepository {
+ constructor(private db: Db) {}
+
+ public async find(objectSearchParams: Partial): Promise {
+ const [object] = await this.db.object.where(objectSearchParams);
+ return object;
+ }
+
+ public async findById(objectId: number): Promise {
+ return this.find({ id: objectId });
+ }
+
+ /**
+ *
+ * @param directory
+ * @param fileName
+ * @param image
+ * @param name
+ * @param quantity
+ * @param price
+ * @param memberId
+ * @param directory
+ * @returns
+ */
+ public async create(
+ directory: string,
+ fileName: string,
+ image: string,
+ texture: string,
+ name: string,
+ quantity: number,
+ price: number,
+ memberId: number,
+ ): Promise {
+ const [object] = await this.db.object.insert({
+ directory: directory,
+ filename: fileName,
+ image: image,
+ texture: texture,
+ name: name,
+ quantity: quantity,
+ price: price,
+ member_id: memberId,
+ });
+
+ return object;
+ }
+
+ public async findByStatus(status: number): Promise {
+ const objects = await this.db.object.where('status', status);
+ return objects;
+ }
+
+ public async update(objectId: number, props: object): Promise {
+ await this.db.object.where({ id: objectId }).update(props);
+ }
+
+ public async updateObjectLimit(objectId: number, limit: number): Promise {
+ await this.db.object.where({ id: objectId }).update('limit', limit);
+ }
+
+ public async increaseObjectQuantity(
+ objectId: number, props: object): Promise {
+ await this.db.object.where({ id: objectId }).update(props);
+ }
+
+ public async updateObjectName(objectId: number, name: string): Promise {
+ await this.db.object.where({ id: objectId }).update('name', name);
+ }
+
+ public async getMallForSale(status: number, mallExpiration: string): Promise {
+ const objects = await this.db.object
+ .where('status', status)
+ .where('mall_expiration', '>', mallExpiration);
+ return objects;
+ }
+
+ public async findAllObjects(
+ column: string,
+ compare: string,
+ content: string,
+ limit: number,
+ offset: number,
+ ): Promise {
+ const objects = await this.db.object
+ .select('object.*')
+ .where(column, compare, content)
+ .limit(limit)
+ .offset(offset);
+ return objects;
+ }
+
+ public async getUserUploadedObjects(userId: number): Promise {
+ const object = await this.db.object
+ .select('object.*', 'member.username')
+ .where('object.member_id', userId)
+ .where('object.status', '>=', 1)
+ .join('member', 'member.id', 'object.member_id');
+ return object;
+ }
+
+ public async getMallObject(objectId: number): Promise {
+ const object = await this.db.object
+ .select('object.*', 'member.username')
+ .where('object.id', objectId)
+ .where('object.status', 1)
+ .join('member', 'member.id', 'object.member_id');
+ return object;
+ }
+
+ public async total(column: string, compare: string, content: string): Promise {
+ return this.db.object.count('id as count').where(column, compare, content);
+ }
+}
diff --git a/api/src/repositories/place/place.repository.ts b/api/src/repositories/place/place.repository.ts
index 8e182e65..d6536a2a 100644
--- a/api/src/repositories/place/place.repository.ts
+++ b/api/src/repositories/place/place.repository.ts
@@ -1,7 +1,7 @@
import { Service } from 'typedi';
import { Db } from '../../db/db.class';
-import {Home, Place} from '../../types/models';
+import {Home, Place, Store} from '../../types/models';
/** Repository for fetching/interacting with place data in the database. */
@Service()
@@ -19,6 +19,14 @@ export class PlaceRepository {
return place;
}
+ public async findBySlug(slug: string): Promise {
+ return this.db.place.where({ slug: slug }).first();
+ }
+
+ public async findAllStores(): Promise {
+ return this.db.place.where({type: 'shop', status: 1});
+ }
+
/**
* Finds a place record which is a home for a given member id
* @param memberId
@@ -28,6 +36,13 @@ export class PlaceRepository {
return place;
}
+ public async findStorageByUserID(memberId: number): Promise {
+ return await this.db.place
+ .select('place.name', 'place.id')
+ .where({type: 'storage', member_id: memberId})
+ .orderBy('place.name', 'asc');
+ }
+
/**
* Creates a new place with the given parameters.
* @param placeParams parameters to be used for the new place
@@ -48,4 +63,35 @@ export class PlaceRepository {
: undefined;
}
+ public async updatePlaces(id: number, column: string, content: string): Promise {
+ await this.db.place
+ .where({id: id})
+ .update(column, content);
+ }
+
+ /**
+ * This is to assist with the pagination of the place search
+ * @param type
+ * @return string
+ */
+ public async totalByType(type: string): Promise {
+ return this.db.place.count('id as count').where('type', type);
+ }
+
+ /**
+ * returns results of places by type (pagination)
+ * @param type
+ * @param limit
+ * @param offset
+ * @returns
+ */
+ public async findByType(type: string, limit: number, offset: number): Promise {
+ return this.db.place
+ .select(['place.*'])
+ .where('place.type', type)
+ .orderBy('place.id')
+ .limit(limit)
+ .offset(offset);
+ }
+
}
diff --git a/api/src/repositories/role-assignment/role-assignment.repository.spec.ts b/api/src/repositories/role-assignment/role-assignment.repository.spec.ts
new file mode 100644
index 00000000..5d964bff
--- /dev/null
+++ b/api/src/repositories/role-assignment/role-assignment.repository.spec.ts
@@ -0,0 +1,24 @@
+import { Container } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import { RoleAssignmentRepository } from './role-assignment.repository';
+
+describe('RoleAssignmentRepository', () => {
+ let db;
+ let service: RoleAssignmentRepository;
+
+ beforeEach(() => {
+ db = {
+ Role: {
+ insert: jest.fn(),
+ },
+ };
+ Container.reset();
+ Container.set(Db, db);
+ service = Container.get(RoleAssignmentRepository);
+ });
+
+ it('should create', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/api/src/repositories/role-assignment/role-assignment.repository.ts b/api/src/repositories/role-assignment/role-assignment.repository.ts
new file mode 100644
index 00000000..f3fa25e1
--- /dev/null
+++ b/api/src/repositories/role-assignment/role-assignment.repository.ts
@@ -0,0 +1,115 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import { knex } from 'knex';
+import { RoleAssignment } from '../../types/models';
+
+/** Repository for fetching/interacting with role assignment data in the database. */
+@Service()
+export class RoleAssignmentRepository {
+ constructor(private db: Db) {}
+
+ public async addDonor(member_id: number, roleId: any): Promise {
+ try{
+ await this.db.knex('role_assignment')
+ .where('member_id', member_id)
+ .whereIn('role_id', [
+ roleId.supporter,
+ roleId.advocate,
+ roleId.devotee,
+ roleId.champion,
+ ])
+ .del();
+ } finally {
+ if (roleId.donorLevel !== undefined) {
+ await this.db.knex('role_assignment').insert({
+ member_id: member_id,
+ role_id: roleId.donorLevel,
+ });
+ }
+ }
+ }
+
+ public async getByMemberId(memberId: number): Promise {
+ const roleResults = await this.db.roleAssignment.where('member_id', memberId);
+ return roleResults;
+ }
+
+ public async getDonor(memberId: number, roleId: any): Promise {
+ return this.db.knex
+ .select('role.name')
+ .from('role_assignment')
+ .innerJoin('role', 'role_assignment.role_id', 'role.id')
+ .where('role_assignment.member_id', memberId)
+ .whereIn('role_id', [
+ roleId.supporter,
+ roleId.advocate,
+ roleId.devotee,
+ roleId.champion,
+ ])
+ .limit(1)
+ .first();
+ }
+
+ public async getRoleNameAndIdByMemberId(memberId: number): Promise {
+ return this.db.knex
+ .distinct(
+ 'role_assignment.role_id as id',
+ 'role.name as name',
+ )
+ .from('role_assignment')
+ .leftJoin('role', 'role_assignment.role_id', 'role.id')
+ .where('role_assignment.member_id', memberId);
+ }
+
+ /**
+ * query finds all users with job who meet pay requirements
+ * the inner join is just there to check for holding a job
+ * then the for function will gather the highest paying role information per user
+ * the for function also packages all the information for the return
+ * @param limit
+ * @returns list of users with jobs that earned pay
+ */
+ public async getMembersDueRoleCredit(limit: number): Promise {
+ const query = await this.db.knex
+ .select(
+ 'member.id',
+ 'member.wallet_id',
+ 'member.xp',
+ )
+ .from('member')
+ .innerJoin('role_assignment', 'member.id', 'role_assignment.member_id')
+ .where('member.status', 1)
+ .whereRaw('DATE(member.last_weekly_role_credit) != DATE(NOW())')
+ .whereRaw('DATE(member.last_daily_login_credit) >= DATE(NOW() - INTERVAL 7 DAY)')
+ .limit(limit)
+ .distinct('member.id');
+
+ const results = [];
+ for (const index in query) {
+ const member_info = query[index];
+ const role_info = await this.db.knex
+ .select(
+ 'role_assignment.role_id',
+ 'role.income_cc',
+ 'role.income_xp',
+ )
+ .from('role_assignment')
+ .innerJoin('role', 'role_assignment.role_id', 'role.id')
+ .where('role_assignment.member_id', member_info.id)
+ .orderBy('role.income_cc','desc')
+ .first();
+ if (role_info) {
+ results[index] = {
+ member_id: member_info.id,
+ role_id: role_info.role_id,
+ wallet_id: member_info.wallet_id,
+ xp: member_info.xp,
+ income_cc: role_info.income_cc,
+ income_xp: role_info.income_xp,
+ };
+ }
+ }
+ return results;
+ }
+}
diff --git a/api/src/repositories/role/role.repository.spec.ts b/api/src/repositories/role/role.repository.spec.ts
new file mode 100644
index 00000000..f936cadc
--- /dev/null
+++ b/api/src/repositories/role/role.repository.spec.ts
@@ -0,0 +1,10 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import { RoleAssignment } from '../../types/models';
+
+/** Repository for fetching/interacting with role assignment data in the database. */
+@Service()
+export class RoleAssignmentRepository {
+ constructor(private db: Db) {}
+}
diff --git a/api/src/repositories/role/role.repository.ts b/api/src/repositories/role/role.repository.ts
new file mode 100644
index 00000000..b8a0702c
--- /dev/null
+++ b/api/src/repositories/role/role.repository.ts
@@ -0,0 +1,26 @@
+import { Service } from 'typedi';
+
+import { Db } from '../../db/db.class';
+import { Role } from '../../types/models';
+
+/** Repository for fetching/interacting with role data in the database. */
+@Service()
+export class RoleRepository {
+ constructor(private db: Db) {
+ this.populateRoleMap();
+ }
+ public roleMap: any = {};
+
+ private async populateRoleMap(): Promise {
+ const roles = await this.findAll();
+
+ roles.forEach(role => {
+ const sanitizedName = role.name.replace(/\s/g, '');
+ this.roleMap[sanitizedName] = role.id;
+ });
+ }
+
+ public async findAll(): Promise {
+ return this.db.role.where({});
+ }
+}
diff --git a/api/src/repositories/transaction/transaction.repository.ts b/api/src/repositories/transaction/transaction.repository.ts
index f3f56d7d..bbecaa02 100644
--- a/api/src/repositories/transaction/transaction.repository.ts
+++ b/api/src/repositories/transaction/transaction.repository.ts
@@ -1,16 +1,11 @@
import { Service } from 'typedi';
import { Db } from '../../db/db.class';
-import {
- Transaction,
- TransactionReason,
- Wallet,
-} from '../../types/models';
+import { Transaction, TransactionReason, Wallet } from '../../types/models';
/** Repository for creating/interacting with transaction/wallet data in the database. */
@Service()
export class TransactionRepository {
-
constructor(private db: Db) {}
/**
@@ -20,8 +15,10 @@ export class TransactionRepository {
* @param amount amount transacted
* @returns promise resolving in the created transaction object, or rejecting on error
*/
- public async createDailyCreditTransaction(walletId: number, amount: number):
- Promise {
+ public async createDailyCreditTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
return await this.db.knex.transaction(async trx => {
const wallet = await trx('wallet').where({ id: walletId }).first();
await trx('wallet')
@@ -53,8 +50,10 @@ export class TransactionRepository {
* @param amount amount transacted
* @returns promise resolving in the created transaction object, or rejecting on error
*/
- public async createHomePurchaseTransaction(walletId: number, amount: number):
- Promise {
+ public async createHomePurchaseTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
return await this.db.knex.transaction(async trx => {
const wallet = await trx('wallet').where({ id: walletId }).first();
await trx('wallet')
@@ -76,8 +75,7 @@ export class TransactionRepository {
* @param amount amount transacted
* @returns promise resolving in the created transaction object, or rejecting on error
*/
- public async createHomeRefundTransaction(walletId: number, amount: number):
- Promise {
+ public async createHomeRefundTransaction(walletId: number, amount: number): Promise {
return await this.db.knex.transaction(async trx => {
const wallet = await trx('wallet').where({ id: walletId }).first();
await trx('wallet')
@@ -91,4 +89,171 @@ export class TransactionRepository {
return this.find({ id: transactionId });
});
}
+ public async createWeeklyRoleCreditTransaction(
+ walletId: number,
+ amount: number,
+ roleId: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const wallet = await trx('wallet').where({ id: walletId }).first();
+ await trx('wallet')
+ .where({ id: walletId })
+ .update({ balance: wallet.balance + amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: `${TransactionReason.WeeklyCredit} for ${roleId}`,
+ recipient_wallet_id: walletId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
+ public async createSystemCreditTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const wallet = await trx('wallet').where({ id: walletId }).first();
+ await trx('wallet')
+ .where({ id: walletId })
+ .update({ balance: wallet.balance + amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: TransactionReason.SystemToMember,
+ recipient_wallet_id: walletId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
+
+ public async createObjectUploadTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const wallet = await trx('wallet').where({ id: walletId }).first();
+ await trx('wallet')
+ .where({ id: walletId })
+ .update({ balance: wallet.balance - amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: TransactionReason.ObjectUpload,
+ recipient_wallet_id: walletId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
+
+ public async createObjectRestockTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const wallet = await trx('wallet').where({ id: walletId }).first();
+ await trx('wallet')
+ .where({ id: walletId })
+ .update({ balance: wallet.balance - amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: TransactionReason.ObjectRestock,
+ recipient_wallet_id: walletId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
+
+ public async createObjectUploadRefundTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const wallet = await trx('wallet').where({ id: walletId }).first();
+ await trx('wallet')
+ .where({ id: walletId })
+ .update({ balance: wallet.balance + amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: TransactionReason.ObjectUploadRefund,
+ recipient_wallet_id: walletId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
+
+ public async createUnsoldObjectRefundTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const wallet = await trx('wallet').where({ id: walletId }).first();
+ await trx('wallet')
+ .where({ id: walletId })
+ .update({ balance: wallet.balance + amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: TransactionReason.ObjectUnsoldInstancesRefund,
+ recipient_wallet_id: walletId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
+
+ public async createObjectPurchaseTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const wallet = await trx('wallet').where({ id: walletId }).first();
+ await trx('wallet')
+ .where({ id: walletId })
+ .update({ balance: wallet.balance - amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: TransactionReason.ObjectPurchase,
+ recipient_wallet_id: walletId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
+
+ public async createObjectProfitTransaction(
+ walletId: number,
+ amount: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const wallet = await trx('wallet').where({ id: walletId }).first();
+ await trx('wallet')
+ .where({ id: walletId })
+ .update({ balance: wallet.balance + amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: TransactionReason.ObjectProfit,
+ recipient_wallet_id: walletId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
+
+ public async createObjectSellTransaction(
+ buyerId: number,
+ sellerId: number,
+ amount: number,
+ ): Promise {
+ return await this.db.knex.transaction(async trx => {
+ const sellerWallet = await trx('wallet').where({ id: sellerId }).first();
+ const buyerWallet = await trx('wallet').where({ id: buyerId }).first();
+ await trx('wallet')
+ .where({ id: sellerId })
+ .update({ balance: sellerWallet.balance + amount });
+ await trx('wallet')
+ .where({ id: buyerId })
+ .update({ balance: buyerWallet.balance - amount });
+ const [transactionId] = await trx('transaction').insert({
+ amount,
+ reason: TransactionReason.ObjectSell,
+ recipient_wallet_id: sellerId,
+ sender_wallet_id: buyerId,
+ });
+ return this.find({ id: transactionId });
+ });
+ }
}
diff --git a/api/src/repositories/wallet/wallet.repository.ts b/api/src/repositories/wallet/wallet.repository.ts
index 5397737a..e9dada64 100644
--- a/api/src/repositories/wallet/wallet.repository.ts
+++ b/api/src/repositories/wallet/wallet.repository.ts
@@ -18,4 +18,8 @@ export class WalletRepository {
const [wallet] = await this.db.wallet.where({ id: walletId });
return wallet;
}
+
+ public async addMoney(walletId: number, newAmount: number): Promise {
+
+ }
}
diff --git a/api/src/routes/admin.routes.ts b/api/src/routes/admin.routes.ts
new file mode 100644
index 00000000..68c5a46e
--- /dev/null
+++ b/api/src/routes/admin.routes.ts
@@ -0,0 +1,30 @@
+import Router from 'express';
+import {adminController} from '../controllers';
+
+const adminRoutes = Router();
+adminRoutes.post('/ban', (request, response) =>
+ adminController.addBan(request, response));
+adminRoutes.post('/donor', (request, response) =>
+ adminController.addDonor(request, response));
+adminRoutes.post('/deleteban', (request, response) =>
+ adminController.deleteBan(request, response));
+adminRoutes.get('/banhistory', (request, response) =>
+ adminController.getBanHistory(request, response));
+adminRoutes.get('/donor', (request, response) =>
+ adminController.getDonor(request, response));
+adminRoutes.get('/usersearch', (request, response) =>
+ adminController.searchUsers(request, response));
+adminRoutes.get('/userchat', (request, response) =>
+ adminController.searchUserChat(request, response));
+adminRoutes.get('/avatars', (request, response) =>
+ adminController.avatars(request, response));
+adminRoutes.post('/avatars/approve', (request, response) =>
+ adminController.avatarApprove(request, response));
+adminRoutes.post('/avatars/reject', (request, response) =>
+ adminController.avatarReject(request, response));
+adminRoutes.get('/places', (request, response) =>
+ adminController.places(request, response));
+adminRoutes.post('/places/update', (request, response) =>
+ adminController.placesUpdate(request, response));
+
+export {adminRoutes};
diff --git a/api/src/routes/avatar.routes.ts b/api/src/routes/avatar.routes.ts
index 059e813e..9145995e 100644
--- a/api/src/routes/avatar.routes.ts
+++ b/api/src/routes/avatar.routes.ts
@@ -5,5 +5,6 @@ import { avatarController} from '../controllers';
const avatarRoutes = Router();
avatarRoutes.get('', (request, response) => avatarController.getResults(request, response));
+avatarRoutes.post('/upload', (request, response) => avatarController.add(request, response));
export { avatarRoutes };
diff --git a/api/src/routes/block.routes.ts b/api/src/routes/block.routes.ts
index 1d833dae..9f0a9770 100644
--- a/api/src/routes/block.routes.ts
+++ b/api/src/routes/block.routes.ts
@@ -1,14 +1,26 @@
-import Router from 'express';
+import Router, {request, response} from 'express';
import { blockController } from '../controllers';
const blockRoutes = Router();
-blockRoutes.get('/:id/locations',
- (request, response) => blockController.getLocations(request, response));
-blockRoutes.get('/:id',
- (request, response) => blockController.getBlock(request, response));
-blockRoutes.post('/:id/locations',
- (request, response) => blockController.postLocations(request, response));
-
+blockRoutes.get('/:id/locations', (request, response) =>
+ blockController.getLocations(request, response),
+);
+blockRoutes.get('/:id', (request, response) => blockController.getBlock(request, response));
+blockRoutes.get('/:id/can_admin', (request, response) =>
+ blockController.canAdmin(request, response),
+);
+blockRoutes.get('/:id/can_manage_access', (request, response) =>
+ blockController.canManageAccess(request, response),
+);
+blockRoutes.post('/:id/locations', (request, response) =>
+ blockController.postLocations(request, response),
+);
+blockRoutes.get('/:id/getAccessInfo', (request, response) =>
+ blockController.getAccessInfoByUsername(request, response),
+);
+blockRoutes.post('/:id/postAccessInfo', (request, response) =>
+ blockController.postAccessInfo(request, response),
+);
export { blockRoutes };
diff --git a/api/src/routes/colony.routes.ts b/api/src/routes/colony.routes.ts
index 22e91a08..0e78324e 100644
--- a/api/src/routes/colony.routes.ts
+++ b/api/src/routes/colony.routes.ts
@@ -1,10 +1,24 @@
import Router from 'express';
-import { colonyController } from '../controllers';
+import {colonyController} from '../controllers';
const colonyRoutes = Router();
-colonyRoutes.get('/:slug/hoods',
- (request, response) => colonyController.getHoods(request, response));
+colonyRoutes.get('/:slug/hoods', (request, response) =>
+ colonyController.getHoods(request, response),
+);
+colonyRoutes.get('/:id/can_admin', (request, response) =>
+ colonyController.canAdmin(request, response),
+);
+
+colonyRoutes.get('/:id/can_manage_access', (request, response) =>
+ colonyController.canManageAccess(request, response),
+);
+colonyRoutes.get('/:id/getAccessInfo', (request, response) =>
+ colonyController.getAccessInfoByUsername(request, response),
+);
+colonyRoutes.post('/:id/postAccessInfo', (request, response) =>
+ colonyController.postAccessInfo(request, response),
+);
export { colonyRoutes };
diff --git a/api/src/routes/fleamarket.routes.ts b/api/src/routes/fleamarket.routes.ts
new file mode 100644
index 00000000..1aae4771
--- /dev/null
+++ b/api/src/routes/fleamarket.routes.ts
@@ -0,0 +1,14 @@
+import Router from 'express';
+
+import { fleamarketController } from '../controllers';
+
+/**
+ * This file sets up routing for home routes.
+ * @note All paths used here will be prepended with `/api/home`.
+ */
+
+const fleamarketRoutes = Router();
+fleamarketRoutes.get('/can_admin', (request, response) =>
+ fleamarketController.canAdmin(request, response));
+
+export { fleamarketRoutes };
diff --git a/api/src/routes/hood.routes.ts b/api/src/routes/hood.routes.ts
index dcc595b5..f5b40b7f 100644
--- a/api/src/routes/hood.routes.ts
+++ b/api/src/routes/hood.routes.ts
@@ -1,12 +1,19 @@
import Router from 'express';
-import { hoodController } from '../controllers';
+import {hoodController} from '../controllers';
const hoodRoutes = Router();
-hoodRoutes.get('/:id/blocks',
- (request, response) => hoodController.getBlocks(request, response));
-hoodRoutes.get('/:id',
- (request, response) => hoodController.getHood(request, response));
-
+hoodRoutes.get('/:id/blocks', (request, response) => hoodController.getBlocks(request, response));
+hoodRoutes.get('/:id', (request, response) => hoodController.getHood(request, response));
+hoodRoutes.get('/:id/can_admin', (request, response) => hoodController.canAdmin(request, response));
+hoodRoutes.get('/:id/can_manage_access', (request, response) =>
+ hoodController.canManageAccess(request, response),
+);
+hoodRoutes.get('/:id/getAccessInfo', (request, response) =>
+ hoodController.getAccessInfoByUsername(request, response),
+);
+hoodRoutes.post('/:id/postAccessInfo', (request, response) =>
+ hoodController.postAccessInfo(request, response),
+);
export { hoodRoutes };
diff --git a/api/src/routes/inbox.routes.ts b/api/src/routes/inbox.routes.ts
new file mode 100644
index 00000000..dac4fb30
--- /dev/null
+++ b/api/src/routes/inbox.routes.ts
@@ -0,0 +1,24 @@
+import Router from 'express';
+
+import { inboxController } from '../controllers';
+
+const inboxRoutes = Router();
+
+inboxRoutes.post('/changeinboxintro/',
+ (request, response) => inboxController.changeInboxIntro(request, response));
+inboxRoutes.post('/deletemessage/',
+ (request, response) => inboxController.deleteInboxMessage(request, response));
+inboxRoutes.post('/info/',
+ (request, response) => inboxController.getInfo(request, response));
+inboxRoutes.post('/getadmininfo/',
+ (request, response) => inboxController.getAdminInfo(request, response));
+inboxRoutes.post('/getmessage/',
+ (request, response) => inboxController.getMessage(request, response));
+inboxRoutes.post('/messages/',
+ (request, response) => inboxController.getInboxMessages(request, response));
+inboxRoutes.post('/postmessage/',
+ (request, response) => inboxController.postInboxMessage(request, response));
+inboxRoutes.post('/postreply/',
+ (request, response) => inboxController.postInboxReply(request, response));
+
+export { inboxRoutes };
diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts
index 588075c2..5c515070 100644
--- a/api/src/routes/index.ts
+++ b/api/src/routes/index.ts
@@ -1,9 +1,15 @@
+export * from './admin.routes';
export * from './avatar.routes';
export * from './block.routes';
export * from './colony.routes';
+export * from './fleamarket.routes';
export * from './home.routes';
export * from './hood.routes';
+export * from './mall.routes';
export * from './member.routes';
export * from './message.routes';
export * from './object-instance.routes';
+export * from './object.routes';
export * from './place.routes';
+export * from './messageboard.routes';
+export * from './inbox.routes';
diff --git a/api/src/routes/mall.routes.ts b/api/src/routes/mall.routes.ts
new file mode 100644
index 00000000..8dd16a91
--- /dev/null
+++ b/api/src/routes/mall.routes.ts
@@ -0,0 +1,44 @@
+import Router from 'express';
+
+import { mallController } from '../controllers';
+
+/**
+ * This file sets up routing for home routes.
+ * @note All paths used here will be prepended with `/api/home`.
+ */
+
+const mallRoutes = Router();
+mallRoutes.get('/can_admin', (request, response) => mallController.canAdmin(request, response));
+mallRoutes.get('/pending_approval', (request, response) =>
+ mallController.objectsPendingApproval(request, response),
+);
+mallRoutes.get('/stores', (request, response) => mallController.findStores(request,response));
+mallRoutes.get('/all_objects', (request, response) =>
+ mallController.findAllObjects(request,response));
+mallRoutes.post('/approve', (request, response) => mallController.approveObject(request, response));
+mallRoutes.post('/reject', (request, response) => mallController.rejectObject(request, response));
+mallRoutes.post('/refund', (request, response) =>
+ mallController.refundUnsoldInstances(request, response));
+mallRoutes.post('/limit', (request, response) =>
+ mallController.updateObjectLimit(request, response));
+mallRoutes.post('/updateObjectName', (request, response) =>
+ mallController.updateObjectName(request, response));
+mallRoutes.post('/drop', (request, response) => mallController.dropMallObject(request, response));
+mallRoutes.post('/remove', (request, response) =>
+ mallController.removeMallObject(request, response));
+mallRoutes.post('/delete', (request, response) =>
+ mallController.deleteMallObject(request, response));
+mallRoutes.get('/objects/:id', (request, response) =>
+ mallController.objectsForSale(request, response),
+);
+mallRoutes.get('/object/:id', (request, response) =>
+ mallController.findByObjectId(request, response));
+mallRoutes.get('/store/:id', (request, response) =>
+ mallController.findStore(request, response));
+mallRoutes.get('/user/:username', (request, response) =>
+ mallController.findByUsername(request, response));
+mallRoutes.post('/:id/position', (request, response) =>
+ mallController.updateObjectPosition(request, response));
+mallRoutes.post('/buy', (request, response) => mallController.buyObject(request, response));
+
+export { mallRoutes };
diff --git a/api/src/routes/member.routes.ts b/api/src/routes/member.routes.ts
index 05f1aea7..88182bf1 100644
--- a/api/src/routes/member.routes.ts
+++ b/api/src/routes/member.routes.ts
@@ -1,4 +1,4 @@
-import Router from 'express';
+import Router, { response } from 'express';
import { memberController } from '../controllers';
@@ -9,19 +9,53 @@ import { memberController } from '../controllers';
const memberRoutes = Router();
memberRoutes.post('/signup', (request, response) => memberController.signup(request, response));
+memberRoutes.post('/is_banned', (request, response) =>
+ memberController.isBanned(request, response),
+);
+memberRoutes.post('/joined', (request, response) =>
+ memberController.joinedPlace(request, response));
+memberRoutes.get('/getrolename', (request, response) =>
+ memberController.getPrimaryRoleName(request, response),
+);
+memberRoutes.get('/getadminlevel', (request, response) =>
+ memberController.getAdminLevel(request, response),
+);
+memberRoutes.get('/getdonorlevel', (request, response) =>
+ memberController.getDonorLevel(request, response),
+);
memberRoutes.post('/login', (request, response) => memberController.login(request, response));
memberRoutes.get('/session', (request, response) => memberController.session(request, response));
-memberRoutes.post('/update_password',
- (request, response) => memberController.updatePassword(request, response));
-memberRoutes.post('/update_avatar',
- (request, response) => memberController.updateAvatar(request, response));
-memberRoutes.post('/send_password_reset',
- (request, response) => memberController.sendPasswordReset(request, response));
-memberRoutes.post('/reset_password',
- (request, response) => memberController.resetPassword(request, response));
-memberRoutes.get('/info',
- (request, response) => memberController.getInfo(request, response));
-memberRoutes.get('/info/:id',
- (request, response) => memberController.getInfo(request, response));
+memberRoutes.post('/update_password', (request, response) =>
+ memberController.updatePassword(request, response),
+);
+memberRoutes.post('/update_role', (request, response) =>
+ memberController.updatePrimaryRoleId(request, response),
+);
+memberRoutes.post('/updateinfo', (request, response) =>
+ memberController.updateInfo(request, response),
+);
+memberRoutes.post('/update_avatar', (request, response) =>
+ memberController.updateAvatar(request, response),
+);
+memberRoutes.post('/send_password_reset', (request, response) =>
+ memberController.sendPasswordReset(request, response),
+);
+memberRoutes.post('/reset_password', (request, response) =>
+ memberController.resetPassword(request, response),
+);
+memberRoutes.get('/info', (request, response) => memberController.getInfo(request, response));
+memberRoutes.get('/storage', (request, response) => memberController.getStorage(request, response));
+memberRoutes.post('/storage/update', (request, response) =>
+ memberController.updateStorage(request, response));
+memberRoutes.post('/ping', (request, response) =>
+ memberController.updateLatestActivity(request, response));
+memberRoutes.get('/info/:id', (request, response) => memberController.getInfo(request, response));
+memberRoutes.get('/roles', (request, response) => memberController.getRoles(request, response));
+memberRoutes.post('/check3d', (request, response) => memberController.check3d(request, response));
+memberRoutes.get('/places', (request, response) =>
+ memberController.getActivePlaces(request, response));
+memberRoutes.get('/backpack/:username', (request, response) =>
+ memberController.getBackpack(request, response),
+);
export { memberRoutes };
diff --git a/api/src/routes/message.routes.ts b/api/src/routes/message.routes.ts
index ab22ba81..cd34ef81 100644
--- a/api/src/routes/message.routes.ts
+++ b/api/src/routes/message.routes.ts
@@ -7,6 +7,8 @@ messageRoutes.get('/place/:placeId',
(request, response) => messageController.getResults(request, response));
messageRoutes.post('/place/:placeId',
(request, response) => messageController.addMessage(request, response));
+messageRoutes.post('/message/:messageid',
+ (request, response) => messageController.deleteMessage(request, response));
export { messageRoutes };
diff --git a/api/src/routes/messageboard.routes.ts b/api/src/routes/messageboard.routes.ts
new file mode 100644
index 00000000..099f372e
--- /dev/null
+++ b/api/src/routes/messageboard.routes.ts
@@ -0,0 +1,24 @@
+import Router from 'express';
+
+import { messageboardController } from '../controllers';
+
+const messageboardRoutes = Router();
+
+messageboardRoutes.post('/changemessageboardintro/',
+ (request, response) => messageboardController.changeMessageboardIntro(request, response));
+messageboardRoutes.post('/deletemessage/',
+ (request, response) => messageboardController.deleteMessageboardMessage(request, response));
+messageboardRoutes.post('/info/',
+ (request, response) => messageboardController.getInfo(request, response));
+messageboardRoutes.post('/getadmininfo/',
+ (request, response) => messageboardController.getAdminInfo(request, response));
+messageboardRoutes.post('/getmessage/',
+ (request, response) => messageboardController.getMessage(request, response));
+messageboardRoutes.post('/messages/',
+ (request, response) => messageboardController.getMessageboardMessages(request, response));
+messageboardRoutes.post('/postmessage/',
+ (request, response) => messageboardController.postMessageboardMessage(request, response));
+messageboardRoutes.post('/postreply/',
+ (request, response) => messageboardController.postMessageboardReply(request, response));
+
+export { messageboardRoutes };
diff --git a/api/src/routes/object-instance.routes.ts b/api/src/routes/object-instance.routes.ts
index 618b193c..c5fa7af3 100644
--- a/api/src/routes/object-instance.routes.ts
+++ b/api/src/routes/object-instance.routes.ts
@@ -3,7 +3,28 @@ import Router from 'express';
import { objectInstanceController } from '../controllers';
const objectInstanceRoutes = Router();
-objectInstanceRoutes.post('/:id/position',
- (request, response) => objectInstanceController.updateObjectInstancePosition(request, response));
+objectInstanceRoutes.post('/:id/position', (request, response) =>
+ objectInstanceController.updateObjectInstancePosition(request, response),
+);
+objectInstanceRoutes.post('/:id/drop', (request, response) =>
+ objectInstanceController.dropObjectInstance(request, response),
+);
+objectInstanceRoutes.post('/:id/pickup', (request, response) =>
+ objectInstanceController.pickUpObjectInstance(request, response),
+);
+objectInstanceRoutes.post('/:id/properties', (request, response) =>
+ objectInstanceController.openObjectProperties(request, response),
+);
+objectInstanceRoutes.post('/update/', (request, response) =>
+ objectInstanceController.updateObjectInstance(request, response),
+);
+objectInstanceRoutes.post('/buy/', (request, response) =>
+ objectInstanceController.buyObjectInstance(request, response),
+);
+objectInstanceRoutes.post('/backpack', (request, response) =>
+ objectInstanceController.moveToBackpack(request, response));
+
+objectInstanceRoutes.post('/storage', (request, response) =>
+ objectInstanceController.moveToStorage(request, response));
export { objectInstanceRoutes };
diff --git a/api/src/routes/object.routes.ts b/api/src/routes/object.routes.ts
new file mode 100644
index 00000000..ee72c3cb
--- /dev/null
+++ b/api/src/routes/object.routes.ts
@@ -0,0 +1,10 @@
+import Router from 'express';
+
+import { objectController } from '../controllers';
+
+const objectRoutes = Router();
+objectRoutes.post('/add', (request, response) => objectController.add(request, response));
+objectRoutes.post('/increase_quantity', (request, response) =>
+ objectController.increaseQuantity(request, response));
+
+export { objectRoutes };
diff --git a/api/src/routes/place.routes.ts b/api/src/routes/place.routes.ts
index 6b0a99eb..0bb83c21 100644
--- a/api/src/routes/place.routes.ts
+++ b/api/src/routes/place.routes.ts
@@ -1,11 +1,15 @@
import Router from 'express';
-import { placeController } from '../controllers';
+import {placeController} from '../controllers';
const placeRoutes = Router();
placeRoutes.get('/:placeId/object_instance',
(request, response) => placeController.getPlaceObjects(request, response));
placeRoutes.get('/:slug',
(request, response) => placeController.getPlace(request, response));
+placeRoutes.get('/:id',
+ (request, response) => placeController.getPlaceById(request, response));
+placeRoutes.post('/add_storage', (request, response) =>
+ placeController.addStorage(request, response));
export { placeRoutes };
diff --git a/api/src/services/admin/admin.services.ts b/api/src/services/admin/admin.services.ts
new file mode 100644
index 00000000..3d38645b
--- /dev/null
+++ b/api/src/services/admin/admin.services.ts
@@ -0,0 +1,113 @@
+import { Service } from 'typedi';
+
+import {
+ BanRepository,
+ MemberRepository,
+ MessageRepository,
+ RoleAssignmentRepository,
+ RoleRepository,
+ AvatarRepository,
+ PlaceRepository,
+} from '../../repositories';
+
+@Service()
+export class AdminService {
+ constructor(
+ private banRepository: BanRepository,
+ private memberRepository: MemberRepository,
+ private messageRepository: MessageRepository,
+ private roleAssignmentRepository: RoleAssignmentRepository,
+ private roleRepository: RoleRepository,
+ private avatarRespository: AvatarRepository,
+ private placeRepository: PlaceRepository,
+ ) {}
+
+ public async addBan(ban_member_id, time_frame, type, assigner_member_id, reason): Promise {
+ const end_date = new Date();
+ end_date.setTime(end_date.getTime() + time_frame * 24 * 60 * 60 * 1000);
+ end_date.getUTCDate();
+ await this.banRepository.addBan(ban_member_id, end_date, type, assigner_member_id, reason);
+ }
+
+ public async addDonor(member_id: number, donor: string): Promise {
+ const donorId = {
+ supporter: await this.roleRepository.roleMap.Supporter,
+ advocate: await this.roleRepository.roleMap.Advocate,
+ devotee: await this.roleRepository.roleMap.Devotee,
+ champion: await this.roleRepository.roleMap.Champion,
+ donorLevel: await this.roleRepository.roleMap[donor],
+ };
+ try {
+ await this.roleAssignmentRepository.addDonor(member_id, donorId);
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ public async deleteBan(banId: number, updateReason: string): Promise{
+ await this.banRepository.deleteBan(banId, updateReason);
+ }
+
+ public async getBanHistory(ban_member_id: number): Promise {
+ return await this.banRepository.getBanHistory(ban_member_id);
+ }
+
+ public async getDonor(member_id: number): Promise {
+ const donorId = {
+ supporter: await this.roleRepository.roleMap.Supporter,
+ advocate: await this.roleRepository.roleMap.Advocate,
+ devotee: await this.roleRepository.roleMap.Devotee,
+ champion: await this.roleRepository.roleMap.Champion,
+ };
+ try {
+ return await this.roleAssignmentRepository.getDonor(member_id, donorId);
+ } catch (e) {
+ console.log(e);
+ }
+ }
+
+ public async searchUsers(search: string, limit: number, offset: number): Promise {
+ const users = await this.memberRepository.searchUsers(search, limit, offset);
+ const total = await this.memberRepository.getTotal(search);
+ return {
+ users: users,
+ total: total,
+ };
+ }
+
+ public async searchUserChat(
+ search: string,
+ user: number,
+ limit: number,
+ offset: number,
+ ): Promise {
+ const messages = await this.messageRepository.searchUserChat(search, user, limit, offset);
+ const total = await this.messageRepository.getChatTotal(search, user);
+ return {
+ messages: messages,
+ total: total,
+ };
+ }
+
+ public async searchAvatars(status: number, limit: number, offset: number): Promise {
+ const avatars = await this.avatarRespository.findByStatus(status, limit, offset);
+ const total = await this.avatarRespository.totalByStatus(status);
+ return {
+ avatars: avatars,
+ total: total,
+ };
+ }
+
+ public async updatePlaces(id: number, column: string, content: string): Promise {
+ await this.placeRepository.updatePlaces(id, column, content);
+ }
+
+ public async searchPlaces(type: string, limit: number, offset: number): Promise {
+ const places = await this.placeRepository.findByType(type, limit, offset);
+ const total = await this.placeRepository.totalByType(type);
+ return {
+ places: places,
+ total: total,
+ };
+ }
+}
diff --git a/api/src/services/avatar/avatar.service.ts b/api/src/services/avatar/avatar.service.ts
new file mode 100644
index 00000000..6d2531d8
--- /dev/null
+++ b/api/src/services/avatar/avatar.service.ts
@@ -0,0 +1,111 @@
+import crypto from 'crypto';
+const fs = require('fs');
+import { Service } from 'typedi';
+import { Avatar } from 'models';
+
+import {
+ AvatarRepository,
+} from '../../repositories';
+
+/** Service for dealing with avatars */
+@Service()
+export class AvatarService {
+ constructor(
+ private avatarRepository: AvatarRepository,
+ ) {}
+
+ public static readonly WRL_FILESIZE_LIMIT = 250000;
+ public static readonly TEXTURE_FILESIZE_LIMIT = 250000;
+ public static readonly IMAGE_FILESIZE_LIMIT = 250000;
+
+ public static readonly STATUS_DELETED = 0;
+ public static readonly STATUS_ACTIVE = 1;
+ public static readonly STATUS_PENDING = 2;
+
+
+ /**
+ * Finds all avatars
+ * @returns promise resolving all avatars object, or rejecting on error
+ */
+ public async findAll(): Promise {
+ return await this.avatarRepository.findAll();
+ }
+
+ /**
+ * Finds all avatars a member id can access
+ * @returns promise resolving all avatars object, or rejecting on error
+ */
+ public async getResults(memberId : number): Promise {
+ return await this.avatarRepository.findAllForMemberId(memberId);
+ }
+
+ /**
+ * create an avatar (file upload and record)
+ * @param wrlFile
+ * @param imageFile
+ * @param textureFile
+ * @param name
+ * @param gestures
+ * @param privateStatus
+ * @param memberId
+ */
+ public async create(wrlFile, imageFile, textureFile, name, gestures, privateStatus, memberId) {
+ let uuid = crypto.randomUUID();
+ let fileName = crypto.randomBytes(8).toString('hex');
+
+ const assets = await this.uploadAvatarFiles(
+ uuid,
+ fileName,
+ wrlFile,
+ imageFile,
+ textureFile ?? null,
+ );
+
+ this.avatarRepository.create(
+ uuid,
+ assets.filename,
+ assets.image,
+ name,
+ gestures,
+ privateStatus,
+ memberId,
+ AvatarService.STATUS_PENDING
+ );
+ }
+
+ public async uploadAvatarFiles(
+ directoryName,
+ fileName,
+ wrlFile,
+ imageFile,
+ textureFile?,
+ ): Promise {
+ let uploadPath = process.env.ASSETS_DIR + '/avatars/' + directoryName;
+ const response = {
+ filename: null,
+ image: null,
+ texture: null,
+ };
+
+ fs.mkdirSync(uploadPath);
+ wrlFile.mv(uploadPath + '/' + fileName + '.wrl');
+ response.filename = fileName + '.wrl';
+
+ let imageExtension = imageFile.name.split('.').pop();
+ imageFile.mv(uploadPath + '/' + fileName + '.' + imageExtension);
+ response.image = fileName + '.' + imageExtension;
+
+ if (textureFile) {
+ textureFile.mv(uploadPath + '/' + textureFile.name);
+ response.texture = textureFile.name;
+ }
+ return response;
+ }
+
+ public async approve(id): Promise {
+ await this.avatarRepository.updateStatus(id,AvatarService.STATUS_ACTIVE);
+ }
+ public async reject(id): Promise {
+ await this.avatarRepository.updateStatus(id,AvatarService.STATUS_DELETED);
+ }
+}
diff --git a/api/src/services/block/block.service.ts b/api/src/services/block/block.service.ts
index 26c6a848..55752571 100644
--- a/api/src/services/block/block.service.ts
+++ b/api/src/services/block/block.service.ts
@@ -2,18 +2,216 @@ import { Service } from 'typedi';
import {
BlockRepository,
+ MapLocationRepository,
+ HoodRepository,
+ RoleAssignmentRepository,
+ RoleRepository,
+ MemberRepository,
} from '../../repositories';
+import {Member, Place} from '../../types/models';
+import {includes} from 'lodash';
/** Service for dealing with blocks */
@Service()
export class BlockService {
-
constructor(
private blockRepository: BlockRepository,
+ private mapLocationRepository: MapLocationRepository,
+ private hoodRepository: HoodRepository,
+ private roleAssignmentRepository: RoleAssignmentRepository,
+ private roleRepository: RoleRepository,
+ private memberRepository: MemberRepository,
) {}
+
+ private async updateDeputyId(deputy: any): Promise {
+ let newDeputies = 0;
+ if (deputy.username !== null) {
+ const result = await this.memberRepository.findIdByUsername(deputy.username);
+ newDeputies = result[0].id;
+ }
+ return newDeputies;
+ }
+
+ public async find(blockId: number): Promise {
+ return await this.blockRepository.find(blockId);
+ }
+
+ public async getHood(blockId: number): Promise {
+ const blockMapLocation = await this.mapLocationRepository.findPlaceIdMapLocation(blockId);
+ return await this.hoodRepository.find(blockMapLocation.parent_place_id);
+ }
+
+ public async getAccessInfoByUsername(blockId: number): Promise {
+ const deputyCode = await this.roleRepository.roleMap.BlockDeputy;
+ const ownerCode = await this.roleRepository.roleMap.BlockLeader;
+ return await this.blockRepository.getAccessInfoByUsername(blockId, ownerCode, deputyCode);
+ }
+
+ public async postAccessInfo(
+ blockId: number,
+ givenDeputies: any,
+ givenOwner: string): Promise {
+ /**
+ * old is coming from database
+ * new is coming from access rights page
+ */
+ const deputyCode = await this.roleRepository.roleMap.BlockDeputy;
+ const ownerCode = await this.roleRepository.roleMap.BlockLeader;
+ let oldOwner = null;
+ let newOwner = null;
+ const oldDeputies = [0,0,0,0,0,0,0,0];
+ const newDeputies = [0,0,0,0,0,0,0,0];
+ const data = await this.blockRepository.getAccessInfoByID(blockId, ownerCode, deputyCode);
+ if (data.owner.length > 0) {
+ oldOwner = data.owner[0].member_id;
+ } else {
+ oldOwner = 0;
+ }
+ try {
+ newOwner = await this.memberRepository.findIdByUsername(givenOwner);
+ newOwner = newOwner[0].id;
+ } catch (error) {
+ newOwner = 0;
+ }
+ if (newOwner !== 0) {
+ if (oldOwner !== 0) {
+ await this.blockRepository.removeIdFromAssignment(blockId, oldOwner, ownerCode);
+ const response: any = await this.memberRepository.getPrimaryRoleName(oldOwner);
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (ownerCode === primaryRoleId){
+ await this.memberRepository.update(oldOwner, {primary_role_id: null});
+ }
+ }
+ }
+ await this.blockRepository.addIdToAssignment(blockId, newOwner, ownerCode);
+ }
+ data.deputies.forEach((deputies, index) => {
+ oldDeputies[index] = deputies.member_id;
+ });
+ for (let i = 0; i < givenDeputies.length; i++) {
+ newDeputies[i] = await this.updateDeputyId(givenDeputies[i]);
+ }
+ oldDeputies.forEach((oldDeputies, index) => {
+ if (oldDeputies !== newDeputies[index]) {
+ if (newDeputies[index] === 0) {
+ try {
+ this.blockRepository.removeIdFromAssignment(blockId, oldDeputies, deputyCode);
+ } catch (e) {
+ console.log(e);
+ }
+ if (oldDeputies !== 0) {
+ this.memberRepository.getPrimaryRoleName(oldDeputies)
+ .then((response: any) => {
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (primaryRoleId && deputyCode === primaryRoleId) {
+ this.memberRepository.update(oldDeputies, {primary_role_id: null});
+ }
+ }
+ });
+ }
+ } else {
+ try {
+ this.blockRepository.removeIdFromAssignment(blockId, oldDeputies, deputyCode);
+ this.memberRepository.getPrimaryRoleName(oldDeputies)
+ .then((response: any) => {
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (deputyCode === primaryRoleId) {
+ this.memberRepository.update(oldDeputies, {primary_role_id: null});
+ }
+ }
+ });
+ this.blockRepository.addIdToAssignment(blockId, newDeputies[index], deputyCode);
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ }
+ });
+ }
public async getMapLocationAndPlaces(blockId: number): Promise {
- const locations = await this.blockRepository.getMapLocationAndPlacesByBlockId(blockId);
- return locations;
+ return await this.blockRepository.getMapLocationAndPlacesByBlockId(blockId);
+ }
+
+ public async resetMapLocationAvailability(blockId: number): Promise {
+ return await this.mapLocationRepository.resetAvailabilityByParentPlaceId(blockId);
+ }
+
+ public async setMapLocationAvailable(blockId: number, location: number): Promise {
+ return await this.mapLocationRepository.createAvailableLocation(blockId, location);
+ }
+
+ public async canAdmin(blockId: number, memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ const hood = await this.getHood(blockId);
+ const hoodMapLocation = await this.mapLocationRepository.findPlaceIdMapLocation(hood.id);
+ const colonyId = hoodMapLocation.parent_place_id;
+
+ if (
+ roleAssignments.find(assignment => {
+ return (
+ [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.CityMayor,
+ this.roleRepository.roleMap.DeputyMayor,
+ ].includes(assignment.role_id) ||
+ ([
+ this.roleRepository.roleMap.ColonyLeader,
+ this.roleRepository.roleMap.ColonyDeputy,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === colonyId) ||
+ ([
+ this.roleRepository.roleMap.NeighborhoodDeputy,
+ this.roleRepository.roleMap.NeighborhoodLeader,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === hood.id) ||
+ ([
+ this.roleRepository.roleMap.BlockDeputy,
+ this.roleRepository.roleMap.BlockLeader,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === blockId)
+ );
+ })
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ public async canManageAccess(blockId: number, memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ const hood = await this.getHood(blockId);
+ const hoodMapLocation = await this.mapLocationRepository.findPlaceIdMapLocation(hood.id);
+ const colonyId = hoodMapLocation.parent_place_id;
+
+ if (
+ roleAssignments.find(assignment => {
+ return (
+ [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.CityMayor,
+ this.roleRepository.roleMap.DeputyMayor,
+ ].includes(assignment.role_id) ||
+ ([
+ this.roleRepository.roleMap.ColonyLeader,
+ this.roleRepository.roleMap.ColonyDeputy,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === colonyId) ||
+ ([
+ this.roleRepository.roleMap.NeighborhoodDeputy,
+ this.roleRepository.roleMap.NeighborhoodLeader,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === hood.id) ||
+ ([this.roleRepository.roleMap.BlockLeader].includes(assignment.role_id) &&
+ assignment.place_id === blockId)
+ );
+ })
+ ) {
+ return true;
+ }
+ return false;
}
}
diff --git a/api/src/services/colony/colony.service.ts b/api/src/services/colony/colony.service.ts
new file mode 100644
index 00000000..e4462afd
--- /dev/null
+++ b/api/src/services/colony/colony.service.ts
@@ -0,0 +1,178 @@
+import { Service } from 'typedi';
+
+import {
+ ColonyRepository,
+ RoleAssignmentRepository,
+ RoleRepository,
+ MemberRepository,
+} from '../../repositories';
+import { Place } from '../../types/models';
+import * as console from 'console';
+import {includes} from 'lodash';
+
+/** Service for dealing with colony */
+@Service()
+export class ColonyService {
+ constructor(
+ private colonyRepository: ColonyRepository,
+ private roleAssignmentRepository: RoleAssignmentRepository,
+ private roleRepository: RoleRepository,
+ private memberRepository: MemberRepository,
+ ) {}
+
+ private async updateDeputyId(deputy: any): Promise {
+ let newDeputies = 0;
+ console.log(deputy.username);
+ if (deputy.username !== null) {
+ if (deputy.username.length > 0) {
+ const result = await this.memberRepository.findIdByUsername(deputy.username);
+ newDeputies = result[0].id;
+ }
+ }
+ return newDeputies;
+ }
+
+ public async find(colonyId: number): Promise {
+ return await this.colonyRepository.find(colonyId);
+ }
+
+ public async getHoods(colonyId: number): Promise {
+ return await this.colonyRepository.getHoods(colonyId);
+ }
+
+ public async getAccessInfoByUsername(colonyId: number): Promise {
+ const deputyCode = await this.roleRepository.roleMap.ColonyDeputy;
+ const ownerCode = await this.roleRepository.roleMap.ColonyLeader;
+ return await this.colonyRepository.getAccessInfoByUsername(colonyId, ownerCode, deputyCode);
+ }
+
+ public async postAccessInfo(
+ colonyId: number,
+ givenDeputies: any,
+ givenOwner: string): Promise {
+ /**
+ * old is coming from database
+ * new is coming from access rights page
+ */
+ const deputyCode = await this.roleRepository.roleMap.ColonyDeputy;
+ const ownerCode = await this.roleRepository.roleMap.ColonyLeader;
+ let oldOwner = null;
+ let newOwner = null;
+ const oldDeputies = [0,0,0,0,0,0,0,0];
+ const newDeputies = [0,0,0,0,0,0,0,0];
+ const data = await this.colonyRepository.getAccessInfoByID(colonyId, ownerCode, deputyCode);
+ if (data.owner.length > 0) {
+ oldOwner = data.owner[0].member_id;
+ } else {
+ oldOwner = 0;
+ }
+ try {
+ newOwner = await this.memberRepository.findIdByUsername(givenOwner);
+ newOwner = newOwner[0].id;
+ } catch (error) {
+ newOwner = 0;
+ }
+ if (newOwner !== 0) {
+ if (oldOwner !== 0) {
+ await this.colonyRepository.removeIdFromAssignment(colonyId, oldOwner, ownerCode);
+ const response: any = await this.memberRepository.getPrimaryRoleName(oldOwner);
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (ownerCode === primaryRoleId){
+ await this.memberRepository.update(oldOwner, {primary_role_id: null});
+ }
+ }
+ }
+ await this.colonyRepository.addIdToAssignment(colonyId, newOwner, ownerCode);
+ }
+ data.deputies.forEach((deputies, index) => {
+ oldDeputies[index] = deputies.member_id;
+ });
+ for (let i = 0; i < givenDeputies.length; i++) {
+ newDeputies[i] = await this.updateDeputyId(givenDeputies[i]);
+ }
+ oldDeputies.forEach((oldDeputies, index) => {
+ if (oldDeputies !== newDeputies[index]) {
+ if (newDeputies[index] === 0) {
+ try {
+ this.colonyRepository.removeIdFromAssignment(colonyId, oldDeputies, deputyCode);
+ } catch (e) {
+ console.log(e);
+ }
+ if (oldDeputies !== 0) {
+ this.memberRepository.getPrimaryRoleName(oldDeputies)
+ .then((response: any) => {
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (primaryRoleId && deputyCode === primaryRoleId) {
+ this.memberRepository.update(oldDeputies, {primary_role_id: null});
+ }
+ }
+ });
+ }
+ } else {
+ try {
+ this.colonyRepository.removeIdFromAssignment(colonyId, oldDeputies, deputyCode);
+ this.memberRepository.getPrimaryRoleName(oldDeputies)
+ .then((response: any) => {
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (deputyCode === primaryRoleId) {
+ this.memberRepository.update(oldDeputies, {primary_role_id: null});
+ }
+ }
+ });
+ this.colonyRepository.addIdToAssignment(colonyId, newDeputies[index], deputyCode);
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ }
+ });
+ }
+
+ public async canAdmin(colonyId: number, memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+
+ if (
+ roleAssignments.find(assignment => {
+ return (
+ [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.CityMayor,
+ this.roleRepository.roleMap.DeputyMayor,
+ ].includes(assignment.role_id) ||
+ ([
+ this.roleRepository.roleMap.ColonyLeader,
+ this.roleRepository.roleMap.ColonyDeputy,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === colonyId)
+ );
+ })
+ ) {
+ return true;
+ }
+ else return false;
+ }
+
+ public async canManageAccess(colonyId: number, memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+
+ if (
+ roleAssignments.find(assignment => {
+ return (
+ [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.CityMayor,
+ this.roleRepository.roleMap.DeputyMayor,
+ ].includes(assignment.role_id) ||
+ ([this.roleRepository.roleMap.ColonyLeader].includes(assignment.role_id) &&
+ assignment.place_id === colonyId)
+ );
+ })
+ ) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/api/src/services/fleamarket/fleamarket.service.ts b/api/src/services/fleamarket/fleamarket.service.ts
new file mode 100644
index 00000000..996889f7
--- /dev/null
+++ b/api/src/services/fleamarket/fleamarket.service.ts
@@ -0,0 +1,31 @@
+import { Service } from 'typedi';
+
+import {
+ RoleAssignmentRepository,
+ RoleRepository,
+} from '../../repositories';
+
+/** Service for dealing with the flea market */
+@Service()
+export class FleaMarketService {
+ constructor(
+ private roleAssignmentRepository: RoleAssignmentRepository,
+ private roleRepository: RoleRepository,
+ ) {}
+
+ public async canAdmin(memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ if (
+ roleAssignments.find(assignment => {
+ return [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.FleaMarketDeputy,
+ this.roleRepository.roleMap.FleaMarketChief,
+ ].includes(assignment.role_id);
+ })
+ ) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/api/src/services/home/home.service.ts b/api/src/services/home/home.service.ts
index c18edf41..741bf4ed 100644
--- a/api/src/services/home/home.service.ts
+++ b/api/src/services/home/home.service.ts
@@ -5,6 +5,8 @@ import {
MapLocationRepository,
HomeDesignRepository,
HomeRepository,
+ RoleAssignmentRepository,
+ RoleRepository,
} from '../../repositories';
import { Place, HomeDesign } from '../../types/models';
@@ -17,6 +19,8 @@ export class HomeService {
private mapLocationRespository: MapLocationRepository,
private homeDesignRespository: HomeDesignRepository,
private homeRepository: HomeRepository,
+ private roleAssignmentRepository: RoleAssignmentRepository,
+ private roleRepository: RoleRepository,
) {}
@@ -36,13 +40,12 @@ export class HomeService {
}
- public async getPlaceHomeDesign(homePlaceId: number): Promise {
- const homeInfo = await this.homeRepository.findById(homePlaceId);
+ public async getPlaceHomeDesign(memberId: number, homePlaceId: number): Promise {
+ const homeInfo = await this.homeRepository.findById(homePlaceId);
return this.homeDesignRespository.find(homeInfo.home_design_id);
-
}
- public getHomeDesign(homeDesignId: string): HomeDesign {
+ public async getHomeDesign(memberId: number, homeDesignId: string): Promise {
return this.homeDesignRespository.find(homeDesignId);
}
diff --git a/api/src/services/hood/hood.service.ts b/api/src/services/hood/hood.service.ts
new file mode 100644
index 00000000..5162ef76
--- /dev/null
+++ b/api/src/services/hood/hood.service.ts
@@ -0,0 +1,195 @@
+import { Service } from 'typedi';
+
+import {
+ MapLocationRepository,
+ HoodRepository,
+ ColonyRepository,
+ RoleAssignmentRepository,
+ RoleRepository,
+ MemberRepository,
+} from '../../repositories';
+import { Place } from '../../types/models';
+import {includes} from 'lodash';
+
+/** Service for dealing with blocks */
+@Service()
+export class HoodService {
+ constructor(
+ private mapLocationRepository: MapLocationRepository,
+ private hoodRepository: HoodRepository,
+ private colonyRepository: ColonyRepository,
+ private roleAssignmentRepository: RoleAssignmentRepository,
+ private roleRepository: RoleRepository,
+ private memberRepository: MemberRepository,
+ ) {}
+
+ private async updateDeputyId(deputy: any): Promise {
+ let newDeputies = 0;
+ if (deputy.username !== null) {
+ const result = await this.memberRepository.findIdByUsername(deputy.username);
+ newDeputies = result[0].id;
+ }
+ return newDeputies;
+ }
+
+ public async find(hoodId: number): Promise {
+ return await this.hoodRepository.find(hoodId);
+ }
+
+ public async getAccessInfoByUsername(hoodId: number): Promise {
+ const deputyCode = await this.roleRepository.roleMap.NeighborhoodDeputy;
+ const ownerCode = await this.roleRepository.roleMap.NeighborhoodLeader;
+ return await this.hoodRepository.getAccessInfoByUsername(hoodId, ownerCode, deputyCode);
+ }
+
+ public async postAccessInfo(
+ hoodId: number,
+ givenDeputies: any,
+ givenOwner: string): Promise {
+ /**
+ * old is coming from database
+ * new is coming from access rights page
+ */
+ const deputyCode = await this.roleRepository.roleMap.NeighborhoodDeputy;
+ const ownerCode = await this.roleRepository.roleMap.NeighborhoodLeader;
+ let oldOwner = null;
+ let newOwner = null;
+ const oldDeputies = [0,0,0,0,0,0,0,0];
+ const newDeputies = [0,0,0,0,0,0,0,0];
+ const data = await this.hoodRepository.getAccessInfoByID(hoodId, ownerCode, deputyCode);
+ if (data.owner.length > 0) {
+ oldOwner = data.owner[0].member_id;
+ } else {
+ oldOwner = 0;
+ }
+ try {
+ newOwner = await this.memberRepository.findIdByUsername(givenOwner);
+ newOwner = newOwner[0].id;
+ } catch (error) {
+ newOwner = 0;
+ }
+ if (newOwner !== 0) {
+ if (oldOwner !== 0) {
+ await this.hoodRepository.removeIdFromAssignment(hoodId, oldOwner, ownerCode);
+ const response: any = await this.memberRepository.getPrimaryRoleName(oldOwner);
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (ownerCode === primaryRoleId){
+ await this.memberRepository.update(oldOwner, {primary_role_id: null});
+ }
+ }
+ }
+ await this.hoodRepository.addIdToAssignment(hoodId, newOwner, ownerCode);
+ }
+ data.deputies.forEach((deputies, index) => {
+ oldDeputies[index] = deputies.member_id;
+ });
+ for (let i = 0; i < givenDeputies.length; i++) {
+ newDeputies[i] = await this.updateDeputyId(givenDeputies[i]);
+ }
+ oldDeputies.forEach((oldDeputies, index) => {
+ if (oldDeputies !== newDeputies[index]) {
+ if (newDeputies[index] === 0) {
+ try {
+ this.hoodRepository.removeIdFromAssignment(hoodId, oldDeputies, deputyCode);
+ } catch (e) {
+ console.log(e);
+ }
+ if (oldDeputies !== 0) {
+ this.memberRepository.getPrimaryRoleName(oldDeputies)
+ .then((response: any) => {
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (primaryRoleId && deputyCode === primaryRoleId) {
+ this.memberRepository.update(oldDeputies, {primary_role_id: null});
+ }
+ }
+ });
+ }
+ } else {
+ try {
+ this.hoodRepository.removeIdFromAssignment(hoodId, oldDeputies, deputyCode);
+ this.memberRepository.getPrimaryRoleName(oldDeputies)
+ .then((response: any) => {
+ if (response.length !== 0) {
+ const primaryRoleId = response[0].primary_role_id;
+ if (deputyCode === primaryRoleId) {
+ this.memberRepository.update(oldDeputies, {primary_role_id: null});
+ }
+ }
+ });
+ this.hoodRepository.addIdToAssignment(hoodId, newDeputies[index], deputyCode);
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ }
+ });
+ }
+
+ public async getColony(hoodId: number): Promise {
+ const hoodMapLocation = await this.mapLocationRepository.findPlaceIdMapLocation(hoodId);
+ return await this.colonyRepository.find(hoodMapLocation.parent_place_id);
+ }
+
+ public async getBlocks(hoodId: number): Promise {
+ return await this.hoodRepository.getBlocks(hoodId);
+ }
+
+ public async canAdmin(hoodId: number, memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ const colony = await this.getColony(hoodId);
+
+ if (
+ roleAssignments.find(assignment => {
+ return (
+ [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.CityMayor,
+ this.roleRepository.roleMap.DeputyMayor,
+ ].includes(assignment.role_id) ||
+ ([
+ this.roleRepository.roleMap.ColonyLeader,
+ this.roleRepository.roleMap.ColonyDeputy,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === colony.id) ||
+ ([
+ this.roleRepository.roleMap.NeighborhoodDeputy,
+ this.roleRepository.roleMap.NeighborhoodLeader,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === hoodId)
+ );
+ })
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ public async canManageAccess(hoodId: number, memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ const colony = await this.getColony(hoodId);
+
+ if (
+ roleAssignments.find(assignment => {
+ return (
+ [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.CityMayor,
+ this.roleRepository.roleMap.DeputyMayor,
+ ].includes(assignment.role_id) ||
+ ([
+ this.roleRepository.roleMap.ColonyLeader,
+ this.roleRepository.roleMap.ColonyDeputy,
+ ].includes(assignment.role_id) &&
+ assignment.place_id === colony.id) ||
+ ([this.roleRepository.roleMap.NeighborhoodLeader].includes(assignment.role_id) &&
+ assignment.place_id === hoodId)
+ );
+ })
+ ) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/api/src/services/inbox/inbox.service.ts b/api/src/services/inbox/inbox.service.ts
new file mode 100644
index 00000000..f836eb3b
--- /dev/null
+++ b/api/src/services/inbox/inbox.service.ts
@@ -0,0 +1,157 @@
+import { Service } from 'typedi';
+
+import { InboxRepository, ColonyRepository } from '../../repositories';
+import sanitizeHtml from 'sanitize-html';
+import { stringify } from 'ts-jest';
+
+/** Service for dealing with messages on message boards */
+@Service()
+export class InboxService {
+ public static readonly MAX_QUERY_LIMIT = 1000;
+ public static readonly VALID_ORDERS = ['id', 'date'];
+ public static readonly VALID_ORDER_DIRECTIONS = ['asc', 'desc'];
+
+ constructor(private inboxRepository: InboxRepository) {}
+
+ public async changeInboxIntro(placeId, Intro): Promise {
+ console.log(`Service${placeId}`);
+ return await this.inboxRepository.changeInboxIntro(placeId, Intro);
+ }
+ public async deleteInboxMessage(messageId): Promise {
+ return await this.inboxRepository.deleteInboxMessage(messageId);
+ }
+
+ public async getAdminInfo(placeId, memberId): Promise {
+ return await this.inboxRepository.getAdminInfo(placeId, memberId);
+ }
+ public async getInfo(placeId: number): Promise {
+ return await this.inboxRepository.getInfo(placeId);
+ }
+
+ public async getInboxMessages(placeId: number): Promise {
+ return await this.inboxRepository.getInboxMessages(placeId);
+ }
+
+ public async postInboxMessage(
+ memberId: number,
+ placeId: number,
+ subject: string,
+ message: string,
+ ): Promise {
+ return await this.inboxRepository.postInboxMessage(memberId, placeId, subject, message);
+ }
+
+ public async postInboxReply(
+ senderMemberId: number,
+ receiverMemberId: number,
+ subject: string,
+ message: string,
+ parentId: number,
+ ): Promise {
+ const [placeId] = await this.inboxRepository.getHomeId(receiverMemberId);
+ if (placeId === undefined) {
+ throw Error('User does not have an inbox setup.');
+ }
+ return await this.inboxRepository.postInboxReply(
+ senderMemberId,
+ placeId.id,
+ subject,
+ message,
+ parentId,
+ );
+ }
+
+ public async sanitize(uncleanInfo: string): Promise {
+ const cleanInfo = sanitizeHtml(uncleanInfo, {
+ allowedTags: [
+ 'address',
+ 'article',
+ 'aside',
+ 'footer',
+ 'header',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'hgroup',
+ 'main',
+ 'nav',
+ 'section',
+ 'blockquote',
+ 'dd',
+ 'div',
+ 'dl',
+ 'dt',
+ 'figcaption',
+ 'figure',
+ 'hr',
+ 'li',
+ 'main',
+ 'ol',
+ 'p',
+ 'pre',
+ 'ul',
+ 'a',
+ 'abbr',
+ 'b',
+ 'bdi',
+ 'bdo',
+ 'br',
+ 'cite',
+ 'code',
+ 'data',
+ 'dfn',
+ 'em',
+ 'i',
+ 'kbd',
+ 'mark',
+ 'q',
+ 'rb',
+ 'rp',
+ 'rt',
+ 'rtc',
+ 'ruby',
+ 's',
+ 'samp',
+ 'small',
+ 'span',
+ 'strong',
+ 'sub',
+ 'sup',
+ 'time',
+ 'u',
+ 'var',
+ 'wbr',
+ 'caption',
+ 'col',
+ 'colgroup',
+ 'table',
+ 'tbody',
+ 'td',
+ 'tfoot',
+ 'th',
+ 'thead',
+ 'tr',
+ 'img',
+ 'font',
+ 'center',
+ 'map',
+ 'area',
+ ],
+ disallowedTagsMode: 'discard',
+ allowedAttributes: {
+ a: ['href', 'name', 'target'],
+ img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
+ font: ['color', 'size'],
+ map: [ 'name' ],
+ area: [ 'alt', 'title', 'href', 'coords', 'shape', 'target', 'class' ],
+ },
+ });
+ return cleanInfo;
+ }
+ public async getMessage(messageId: number): Promise {
+ return await this.inboxRepository.getMessage(messageId);
+ }
+}
diff --git a/api/src/services/index.ts b/api/src/services/index.ts
index 3ef3af95..79192d05 100644
--- a/api/src/services/index.ts
+++ b/api/src/services/index.ts
@@ -1,3 +1,18 @@
+export * from './admin/admin.services';
+export * from './avatar/avatar.service';
+export * from './block/block.service';
+export * from './colony/colony.service';
+export * from './fleamarket/fleamarket.service';
export * from './home/home.service';
+export * from './hood/hood.service';
+export * from './mall/mall.service';
export * from './member/member.service';
-export * from './block/block.service';
+export * from './message/message.service';
+export * from './object/object.service';
+export * from './object-instance/object-instance.service';
+export * from './role/role.service';
+export * from './role-assignment/role-assignment.service';
+export * from './place/place.service';
+export * from './wallet/wallet.service';
+export * from './messageboard/messageboard.service';
+export * from './inbox/inbox.service';
diff --git a/api/src/services/mall/mall.service.ts b/api/src/services/mall/mall.service.ts
new file mode 100644
index 00000000..6dc453db
--- /dev/null
+++ b/api/src/services/mall/mall.service.ts
@@ -0,0 +1,123 @@
+import { Service } from 'typedi';
+
+import {
+ RoleAssignmentRepository,
+ RoleRepository,
+ ObjectInstanceRepository,
+ ObjectRepository,
+ PlaceRepository,
+ MallRepository,
+ MemberRepository
+} from '../../repositories';
+import { MallObjectPosition, MallObjectRotation } from 'models';
+
+/** Service for dealing with the mall */
+@Service()
+export class MallService {
+ constructor(
+ private roleAssignmentRepository: RoleAssignmentRepository,
+ private roleRepository: RoleRepository,
+ private objectRepository: ObjectRepository,
+ private objectInstanceRepository: ObjectInstanceRepository,
+ private placeRepository: PlaceRepository,
+ private mallRepository: MallRepository,
+ private memberRepository: MemberRepository,
+ ) {}
+
+ public async canAdmin(memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ if (
+ roleAssignments.find(assignment => {
+ return [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.MallDeputy,
+ this.roleRepository.roleMap.MallManager,
+ ].includes(assignment.role_id);
+ })
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ public async isObjectAvailable(objectId: number): Promise {
+ const object = await this.objectRepository.find({ id: objectId });
+ if (!object) {
+ return false;
+ }
+ const instances = await this.objectInstanceRepository.countByObjectId(objectId);
+
+ if (object.status !== 1) {
+ return false;
+ }
+
+ if (instances >= object.quantity) {
+ return false;
+ }
+ return true;
+ }
+
+ public async getMallStores(){
+ const stores = await this.placeRepository.findAllStores();
+ return stores;
+ }
+
+ public async getAllObjects(
+ column: string, compare: string, content: string, limit: number, offset: number){
+ const returnObjects= [];
+ const objects = await this.objectRepository
+ .findAllObjects(column, compare, content, limit, offset);
+ objects.forEach(obj => {
+ const user = this.memberRepository.findById(obj.member_id);
+ const store = this.mallRepository.getStore(obj.id);
+ if(store){
+ store.then((value) => {
+ obj.store = value[0];
+ });
+ }
+ user.then((value) => {
+ obj.username = value.username;
+ });
+ const instances = this.objectInstanceRepository.countByObjectId(obj.id);
+ instances.then((value) => {
+ obj.instances = value;
+ });
+ returnObjects.push(obj);
+ });
+
+ const total = await this.objectRepository.total(column, compare, content);
+ return {
+ objects: returnObjects,
+ total: total,
+ };
+ }
+
+ public async getStore(id: number){
+ const stores = await this.mallRepository.getStore(id);
+ return stores;
+ }
+
+ public async updateObjectPlacement(
+ mallObjectId: number,
+ positionObj: MallObjectPosition,
+ rotationObj: MallObjectRotation,
+ ): Promise {
+ const position = JSON.stringify({
+ x: Number.parseFloat(positionObj.x),
+ y: Number.parseFloat(positionObj.y),
+ z: Number.parseFloat(positionObj.z),
+ });
+ const rotation = JSON.stringify({
+ x: Number.parseFloat(rotationObj.x),
+ y: Number.parseFloat(rotationObj.y),
+ z: Number.parseFloat(rotationObj.z),
+ angle: Number.parseFloat(rotationObj.angle),
+ });
+
+ return await this.mallRepository.updateObjectPlacement(
+ mallObjectId,
+ position,
+ rotation,
+ );
+ }
+}
diff --git a/api/src/services/member/member.service.ts b/api/src/services/member/member.service.ts
index b58641bc..ad0f0ca4 100644
--- a/api/src/services/member/member.service.ts
+++ b/api/src/services/member/member.service.ts
@@ -6,14 +6,18 @@ import { Service } from 'typedi';
import {
AvatarRepository,
+ BanRepository,
+ MapLocationRepository,
MemberRepository,
+ PlaceRepository,
+ RoleAssignmentRepository,
+ RoleRepository,
TransactionRepository,
WalletRepository,
- PlaceRepository,
- MapLocationRepository,
+ ObjectInstanceRepository,
} from '../../repositories';
-import { Member, Place } from '../../types/models';
-import { MemberInfoView } from '../../types/views';
+import { Member } from '../../types/models';
+import { MemberInfoView, MemberAdminView } from '../../types/views';
import { SessionInfo } from 'session-info.interface';
import { Request, Response } from 'express';
@@ -24,6 +28,10 @@ export class MemberService {
public static readonly DAILY_CC_AMOUNT = 50;
/** Amount of experience points a member received each day they log in */
public static readonly DAILY_XP_AMOUNT = 5;
+ /** Amount of cityccash an employed member receives each day they log in */
+ public static readonly DAILY_CC_EMPLOYED_AMOUNT = 100;
+ /** Amount of experience points an employed member received each day they log in */
+ public static readonly DAILY_XP_EMPLOYED_AMOUNT = 10;
/** Duration in minutes until a password reset attempt expires */
public static readonly PASSWORD_RESET_EXPIRATION_DURATION = 15;
/** Number of times to salt member passwords */
@@ -31,13 +39,74 @@ export class MemberService {
constructor(
private avatarRepository: AvatarRepository,
+ private banRepository: BanRepository,
private memberRepository: MemberRepository,
private transactionRepository: TransactionRepository,
private walletRepository: WalletRepository,
private placeRepository: PlaceRepository,
private mapLocationRespository: MapLocationRepository,
+ private roleAssignmentRepository: RoleAssignmentRepository,
+ private objectInstanceRepository: ObjectInstanceRepository,
+ private roleRepository: RoleRepository,
) {}
+ public async canAdmin(memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ // Extracted admin roles into a constant for easy management
+ const ADMIN_ROLES = [
+ this.roleRepository.roleMap.Admin,
+ this.roleRepository.roleMap.CityMayor,
+ this.roleRepository.roleMap.DeputyMayor,
+ this.roleRepository.roleMap.CityCouncil,
+ this.roleRepository.roleMap.SecurityCaptain,
+ this.roleRepository.roleMap.SecurityChief,
+ this.roleRepository.roleMap.SecurityLieutenant,
+ this.roleRepository.roleMap.SecurityOfficer,
+ this.roleRepository.roleMap.SecuritySergeant,
+ ];
+ return !!roleAssignments.find(assignment => ADMIN_ROLES.includes(assignment.role_id));
+ }
+
+ public async canStaff(memberId: number): Promise {
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ // Extracted staff roles into a constant for easy management
+ const STAFF_ROLES = [
+ this.roleRepository.roleMap.ColonyLeader,
+ this.roleRepository.roleMap.ColonyDeputy,
+ this.roleRepository.roleMap.NeighborhoodLeader,
+ this.roleRepository.roleMap.NeighborhoodDeputy,
+ this.roleRepository.roleMap.BlockLeader,
+ this.roleRepository.roleMap.BlockDeputy,
+ ];
+ return !!roleAssignments.find(assignment => STAFF_ROLES.includes(assignment.role_id));
+ }
+
+ public async joinedPlace(id: number, placeId: number, is3d: number): Promise {
+ const now = new Date();
+ await this.memberRepository.joinedPlace(id, {
+ place_id: placeId,
+ is_3d: is3d,
+ last_activity: now,
+ });
+ }
+
+ public async getAccessLevel(memberId: number): Promise {
+ const access = await this.canAdmin(memberId);
+ const roleAssignments = await this.roleAssignmentRepository.getByMemberId(memberId);
+ const admin = !!roleAssignments.find(
+ assignment => assignment.role_id === this.roleRepository.roleMap.Admin,
+ );
+ let accessLevel;
+ if (access && admin) {
+ accessLevel = 'admin';
+ } else if (access) {
+ accessLevel = 'security';
+ } else {
+ accessLevel = 'none';
+ }
+ return accessLevel;
+ }
+
/**
* Creates a new member with the given email, username, and password. If successful, distributes
* daily login bonuses, and returns an encoded member token.
@@ -46,8 +115,12 @@ export class MemberService {
* @param password raw member password
* @returns promise resolving in the session token for the newly created member
*/
- public async createMemberAndLogin(email: string, username: string, password: string):
- Promise {
+
+ public async createMemberAndLogin(
+ email: string,
+ username: string,
+ password: string,
+ ): Promise {
const hashedPassword = await this.encryptPassword(password);
const memberId = await this.memberRepository.create({
email,
@@ -64,7 +137,7 @@ export class MemberService {
* @returns decoded session info
*/
public decodeMemberToken(token: string): SessionInfo {
- return ( jwt.verify(token, process.env.JWT_SECRET));
+ return jwt.verify(token, process.env.JWT_SECRET);
}
/**
@@ -102,6 +175,16 @@ export class MemberService {
return this.memberRepository.findByPasswordResetToken(resetToken);
}
+ public async getDonorLevel(memberId: number): Promise {
+ const donorId = {
+ supporter: await this.roleRepository.roleMap.Supporter,
+ advocate: await this.roleRepository.roleMap.Advocate,
+ devotee: await this.roleRepository.roleMap.Devotee,
+ champion: await this.roleRepository.roleMap.Champion,
+ };
+ return await this.roleAssignmentRepository.getDonor(memberId, donorId);
+ }
+
/**
* Builds a member info view.
* @param memberId id of member to retrieve info for
@@ -118,8 +201,15 @@ export class MemberService {
xp: member.xp,
firstName: member.firstname,
lastName: member.lastname,
+ chatdefault: member.chatdefault,
+ primary_role_id: member.primary_role_id,
};
}
+
+ public async getMemberChat(memberId: number): Promise {
+ const member = await this.find({ id: memberId });
+ return member.chatdefault;
+ }
/**
* Builds a member info public view.
@@ -134,6 +224,7 @@ export class MemberService {
immigrationDate: member.created_at,
username: member.username,
xp: member.xp,
+ chatdefault: member.chatdefault,
};
}
@@ -142,7 +233,7 @@ export class MemberService {
* @param memberId id of member to retrieve info for
* @returns promise resolving in a member info view object, or rejecting on error
*/
- public async getMemberInfoAdmin(memberId: number): Promise {
+ public async getMemberInfoAdmin(memberId: number): Promise {
const member = await this.find({ id: memberId });
const wallet = await this.walletRepository.findById(member.wallet_id);
return {
@@ -153,6 +244,10 @@ export class MemberService {
xp: member.xp,
firstName: member.firstname,
lastName: member.lastname,
+ chatdefault: member.chatdefault,
+ last_daily_login_credit: member.last_daily_login_credit,
+ last_weekly_role_credit: member.last_weekly_role_credit,
+ lastAccess: member.last_activity,
};
}
@@ -166,6 +261,15 @@ export class MemberService {
return this.encodeMemberToken(member);
}
+ public async getPrimaryRoleName(memberId: number): Promise {
+ return this.memberRepository.getPrimaryRoleName(memberId);
+ }
+
+ public async getRoles(memberId: number): Promise {
+ const roles = await this.roleAssignmentRepository.getRoleNameAndIdByMemberId(memberId);
+ return roles;
+ }
+
/**
* Determines if the member with the given id has received their daily login bonus since the
* beginning (00:00:00) of the current day.
@@ -173,7 +277,7 @@ export class MemberService {
* @returns `true` if the member has received their daily login bonus today, `false` otherwise
*/
public hasReceivedLoginCreditToday(member: Member): boolean {
- const today = new Date().setHours(0, 0, 0, 0);
+ const today = new Date().setHours(0, 0, 0, 0);
return member.last_daily_login_credit.getTime() >= today;
}
@@ -187,17 +291,39 @@ export class MemberService {
return member.admin;
}
+ /**
+ * Checks if the user is currently banned
+ * @param memberId
+ * @return banned boolean true if banned
+ */
+ public async isBanned(memberId: number): Promise {
+ let banned = false;
+ const member = await this.memberRepository.findById(memberId);
+ const banInfo = await this.banRepository.getBanMaxDate(memberId);
+ if (typeof banInfo !== 'undefined') {
+ const endDate = new Date(banInfo.end_date);
+ const currentDate = new Date();
+ if (member.status === 0 || endDate > currentDate) {
+ banned = true;
+ }
+ } else {
+ if (member.status === 0) banned = true;
+ }
+ return { banned, banInfo };
+ }
+
/**
* Validates the given username and password and logs a user in.
* @param username username of member to be logged in
* @param password password of member to be logged in
- * @returns
+ * @returns
*/
public async login(username: string, password: string): Promise {
const member = await this.memberRepository.find({ username });
if (!member) throw new Error('Account not found.');
const validPassword = await bcrypt.compare(password, member.password);
if (!validPassword) throw new Error('Incorrect login details.');
+ if (member.status === 0) throw new Error('banned');
this.maybeGiveDailyCredits(member.id);
return this.encodeMemberToken(member);
}
@@ -211,17 +337,20 @@ export class MemberService {
public async maybeGiveDailyCredits(memberId: number): Promise {
const member = await this.memberRepository.findById(memberId);
if (!this.hasReceivedLoginCreditToday(member)) {
- await this.transactionRepository.createDailyCreditTransaction(
- member.wallet_id,
- MemberService.DAILY_CC_AMOUNT,
- );
- await this.memberRepository.update(
- memberId,
- {
- last_daily_login_credit: new Date(),
- xp: member.xp + MemberService.DAILY_XP_AMOUNT,
- },
- );
+ let ccIncrease = MemberService.DAILY_CC_AMOUNT;
+ let xpIncrease = MemberService.DAILY_XP_AMOUNT;
+
+ const roles = await this.roleAssignmentRepository.getByMemberId(memberId);
+ if (roles.length > 0) {
+ ccIncrease = MemberService.DAILY_CC_EMPLOYED_AMOUNT;
+ xpIncrease = MemberService.DAILY_XP_EMPLOYED_AMOUNT;
+ }
+
+ await this.transactionRepository.createDailyCreditTransaction(member.wallet_id, ccIncrease);
+ await this.memberRepository.update(memberId, {
+ last_daily_login_credit: new Date(),
+ xp: member.xp + xpIncrease,
+ });
}
}
@@ -233,11 +362,10 @@ export class MemberService {
* error
*/
public async updateAvatar(memberId: number, avatarId: number): Promise {
- const avatar = await this.avatarRepository.find({
- id: avatarId,
- status: 1,
- private: false,
- });
+ const avatar = await this.avatarRepository.getByIdAndMemberId(
+ avatarId,
+ memberId
+ );
if (_.isUndefined(avatar)) throw new Error(`No avatar exists with id ${avatarId}`);
await this.memberRepository.update(memberId, { avatar_id: avatarId });
}
@@ -254,6 +382,10 @@ export class MemberService {
await this.memberRepository.update(memberId, { password: hashedPassword });
}
+ public async updatePrimaryRoleId(memberId: number, primaryRoleId: number): Promise {
+ await this.memberRepository.update(memberId, { primary_role_id: primaryRoleId });
+ }
+
/**
* Encodes a JSON web token for the member with the given memberId.
* @param member member object to encode a token for
@@ -280,12 +412,14 @@ export class MemberService {
private encryptPassword(password: string): Promise {
return bcrypt.hash(password, MemberService.SALT_ROUNDS);
}
-
+
/**
- * Updates a members first and last name
+ * Updates a members default chat choice firstname and lastname
* @param memberId id of the member
* @param firstName string of the first name
* @param lastName string of the last name
+ * @param chatdefault string of the chatdefault
+ * Must retain updateName here for first time home creation firstname/lastname addition
*/
public async updateName(memberId: number, firstName: string, lastName: string): Promise {
await this.memberRepository.update(memberId, {
@@ -293,6 +427,14 @@ export class MemberService {
lastname: lastName,
});
}
+ public async updateInfo(
+ memberId: number, firstName: string, lastName: string, chatdefault: number): Promise {
+ await this.memberRepository.update(memberId, {
+ firstname: firstName,
+ lastname: lastName,
+ chatdefault: chatdefault,
+ });
+ }
/**
* Deducts the amount for a house purchase from a member's wallet
@@ -314,22 +456,111 @@ export class MemberService {
await this.transactionRepository.createHomeRefundTransaction(member.wallet_id, amount);
}
+ public async getMemberId(username: string): Promise {
+ const userId = await this.memberRepository.findIdByUsername(username);
+ return userId;
+ }
+
+ public async check3d(username: string): Promise {
+ const user = await this.memberRepository.check3d(username);
+ return user;
+ }
+
+ public async updateLatestActivity(memberId: number): Promise {
+ const now = new Date();
+ await this.memberRepository.updateLatestActivity(memberId, {
+ last_activity: now,
+ });
+ }
+
+ public async getActivePlaces(): Promise {
+ const returnPlaces = [];
+ const placeIds = [];
+ const activeTime = new Date(Date.now() - 5 * 60000);
+ const places = await this.memberRepository.getActivePlaces(activeTime);
+ for (const place of places) {
+ if(placeIds.indexOf(place.place_id) === -1){
+ placeIds.push(place.place_id);
+ const userPlace = await this.placeRepository.findById(place.place_id);
+ const userCount = await this.memberRepository.countByPlaceId(place.place_id, activeTime);
+ place.name = userPlace.name;
+ place.slug = userPlace.slug;
+ place.type = userPlace.type;
+ if(userPlace.member_id){
+ const userOwner = await this.memberRepository.findById(userPlace.member_id);
+ place.username = userOwner.username;
+ }
+ place.count = userCount[0].count;
+ returnPlaces.push(place);
+ }
+ }
+ return returnPlaces;
+ }
+
/**
* Attempts to decode the session token present in the request and automatically responds with a
* 400 error if decryption is unsuccessful
- * @param request express request object
+ * @param request Express request object
+ * @param response Express response object used for sending error messages in case of token
+ * decryption failure
* @returns session info object if decoding was successful, `void` otherwise
*/
public decryptSession(request: Request, response: Response): SessionInfo {
const { apitoken } = request.headers;
- const session = this.decodeMemberToken(