diff --git a/migrations/create-tables.js.sql b/migrations/create-tables.js.sql index 18f130d1..f01bcc2a 100644 --- a/migrations/create-tables.js.sql +++ b/migrations/create-tables.js.sql @@ -1,7 +1,8 @@ CREATE TABLE IF NOT EXISTS directus_users ( id CHAR(36) PRIMARY KEY, github_username VARCHAR(255), - user_type VARCHAR(255) NOT NULL DEFAULT 'member' + user_type VARCHAR(255) NOT NULL DEFAULT 'member', + public_probes BOOLEAN DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS gp_adopted_probes ( @@ -34,7 +35,7 @@ CREATE TABLE IF NOT EXISTS gp_adopted_probes ( asn INTEGER NOT NULL, network VARCHAR(255) NOT NULL, countryOfCustomCity VARCHAR(255) -); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS directus_notifications ( id CHAR(10), @@ -42,7 +43,7 @@ CREATE TABLE IF NOT EXISTS directus_notifications ( timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, subject VARCHAR(255), message TEXT -); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE `gp_tokens` ( `date_created` timestamp NULL DEFAULT NULL, @@ -73,7 +74,7 @@ CREATE TABLE IF NOT EXISTS gp_credits ( user_id VARCHAR(36) NOT NULL, CONSTRAINT gp_credits_user_id_unique UNIQUE (user_id), CONSTRAINT gp_credits_amount_positive CHECK (`amount` >= 0) -); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE IF NOT EXISTS gp_location_overrides ( id INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY, @@ -87,4 +88,4 @@ CREATE TABLE IF NOT EXISTS gp_location_overrides ( country VARCHAR(255), latitude FLOAT(10, 5), longitude FLOAT(10, 5) -); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/src/lib/override/adopted-probes.ts b/src/lib/override/adopted-probes.ts index 093e9b20..548776d0 100644 --- a/src/lib/override/adopted-probes.ts +++ b/src/lib/override/adopted-probes.ts @@ -13,6 +13,7 @@ const logger = scopedLogger('adopted-probes'); export const ADOPTIONS_TABLE = 'gp_adopted_probes'; export const NOTIFICATIONS_TABLE = 'directus_notifications'; +export const USERS_TABLE = 'directus_users'; export type Adoption = { id: string; @@ -29,8 +30,8 @@ export type Adoption = { systemTags: string[]; isCustomCity: boolean; status: string; - isIPv4Supported: boolean, - isIPv6Supported: boolean, + isIPv4Supported: boolean; + isIPv6Supported: boolean; version: string | null; nodeVersion: string | null; hardwareDevice: string | null; @@ -43,15 +44,18 @@ export type Adoption = { longitude: number | null; asn: number | null; network: string | null; + githubUsername: string | null; + publicProbes: boolean; } -type Row = Omit & { +export type Row = Omit & { altIps: string; tags: string; systemTags: string; isCustomCity: number; isIPv4Supported: number; isIPv6Supported: number; + publicProbes: number; } type AdoptionFieldDescription = { @@ -166,16 +170,20 @@ export class AdoptedProbes { }; } - getUpdatedTags (probe: Probe) { + getUpdatedTags (probe: Probe): Tag[] { const adoption = this.getByIp(probe.ipAddress); - if (!adoption || !adoption.tags.length) { + if (!adoption || (!adoption.tags.length && !adoption.publicProbes)) { return probe.tags; } return [ ...probe.tags, ...adoption.tags, + ...(adoption.publicProbes && adoption.githubUsername ? [{ + type: 'user' as const, + value: `u-${adoption.githubUsername}`, + }] : []), ]; } @@ -187,13 +195,6 @@ export class AdoptedProbes { return probe; } - const isCustomCity = adoption.isCustomCity; - const hasUserTags = adoption.tags && adoption.tags.length; - - if (!isCustomCity && !hasUserTags) { - return { ...probe, owner: { id: adoption.userId } }; - } - const newLocation = this.getUpdatedLocation(probe.ipAddress, probe.location) || probe.location; const newTags = this.getUpdatedTags(probe); @@ -242,10 +243,11 @@ export class AdoptedProbes { public async fetchAdoptions () { const rows = await this.sql(ADOPTIONS_TABLE) + .leftJoin(USERS_TABLE, `${ADOPTIONS_TABLE}.userId`, `${USERS_TABLE}.id`) // First item will be preserved, so we are prioritizing online probes. // Sorting by id at the end so order is the same in any table state. - .orderByRaw(`lastSyncDate DESC, onlineTimesToday DESC, FIELD(status, 'ready') DESC, id DESC`) - .select(); + .orderByRaw(`${ADOPTIONS_TABLE}.lastSyncDate DESC, ${ADOPTIONS_TABLE}.onlineTimesToday DESC, FIELD(${ADOPTIONS_TABLE}.status, 'ready') DESC, ${ADOPTIONS_TABLE}.id DESC`) + .select(`${ADOPTIONS_TABLE}.*`, `${USERS_TABLE}.github_username AS githubUsername`, `${USERS_TABLE}.public_probes as publicProbes`); const adoptions: Adoption[] = rows.map(row => ({ ...row, @@ -258,6 +260,7 @@ export class AdoptedProbes { isIPv6Supported: Boolean(row.isIPv6Supported), latitude: row.latitude ? normalizeCoordinate(row.latitude) : row.latitude, longitude: row.longitude ? normalizeCoordinate(row.longitude) : row.longitude, + publicProbes: Boolean(row.publicProbes), })); this.adoptions = adoptions; diff --git a/test/tests/unit/override/adopted-probes.test.ts b/test/tests/unit/override/adopted-probes.test.ts index ef94d290..b01252ea 100644 --- a/test/tests/unit/override/adopted-probes.test.ts +++ b/test/tests/unit/override/adopted-probes.test.ts @@ -1,14 +1,14 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import relativeDayUtc from 'relative-day-utc'; -import { AdoptedProbes } from '../../../../src/lib/override/adopted-probes.js'; +import { AdoptedProbes, Row } from '../../../../src/lib/override/adopted-probes.js'; import type { Probe } from '../../../../src/probe/types.js'; describe('AdoptedProbes', () => { - const defaultAdoptedProbe = { + const defaultAdoptedProbe: Row = { id: 'p-1', + name: 'probe-1', userId: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45', - username: 'jimaek', ip: '1.1.1.1', altIps: '[]', uuid: '1-1-1-1-1', @@ -31,6 +31,8 @@ describe('AdoptedProbes', () => { longitude: -6.25, asn: 16509, network: 'Amazon.com, Inc.', + githubUsername: 'jimaek', + publicProbes: 0, }; const defaultConnectedProbe: Probe = { @@ -89,6 +91,7 @@ describe('AdoptedProbes', () => { where: sandbox.stub(), orWhere: sandbox.stub(), whereIn: sandbox.stub(), + leftJoin: sandbox.stub(), orderByRaw: sandbox.stub(), } as any; const sqlStub = sandbox.stub() as any; @@ -104,6 +107,7 @@ describe('AdoptedProbes', () => { sql.where.returns(sql); sql.orWhere.returns(sql); sql.whereIn.returns(sql); + sql.leftJoin.returns(sql); sql.orderByRaw.returns(sql); sql.select.resolves([ defaultAdoptedProbe ]); sqlStub.returns(sql); @@ -420,13 +424,13 @@ describe('AdoptedProbes', () => { expect(sql.raw.args[0]![1]).to.deep.equal({ recipient: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45', - subject: `Your probe's location has changed`, - message: 'Globalping detected that your [probe with IP address **1.1.1.1**](/probes/p-1) has changed its location from Ireland to United Kingdom. The custom city value "Dublin" is not applied anymore.\n\nIf this change is not right, please report it in [this issue](https://github.com/jsdelivr/globalping/issues/268).', + subject: 'Your probe\'s location has changed', + message: 'Globalping detected that your probe [**probe-1**](/probes/p-1) with IP address **1.1.1.1** has changed its location from Ireland to United Kingdom. The custom city value "Dublin" is not applied anymore.\n\nIf this change is not right, please report it in [this issue](https://github.com/jsdelivr/globalping/issues/268).', }); expect(sql.raw.args[1]![1]).to.deep.equal({ recipient: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45', - subject: `Your probe's location has changed`, + subject: 'Your probe\'s location has changed', message: 'Globalping detected that your probe [**probe-gb-london-01**](/probes/p-9) with IP address **9.9.9.9** has changed its location from Ireland to United Kingdom. The custom city value "Dublin" is not applied anymore.\n\nIf this change is not right, please report it in [this issue](https://github.com/jsdelivr/globalping/issues/268).', }); @@ -524,8 +528,8 @@ describe('AdoptedProbes', () => { expect(sql.raw.args[2]![1]).to.deep.equal({ recipient: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45', - subject: `Your probe's location has changed back`, - message: 'Globalping detected that your [probe with IP address **1.1.1.1**](/probes/p-1) has changed its location back from United Kingdom to Ireland. The custom city value "Dublin" is now applied again.', + subject: 'Your probe\'s location has changed back', + message: 'Globalping detected that your probe [**probe-1**](/probes/p-1) with IP address **1.1.1.1** has changed its location back from United Kingdom to Ireland. The custom city value "Dublin" is now applied again.', }); expect(sql.raw.args[3]![1]).to.deep.equal({ @@ -770,11 +774,11 @@ describe('AdoptedProbes', () => { isCustomCity: false, isIPv4Supported: true, isIPv6Supported: true, + publicProbes: false, }); }); it('getUpdatedLocation method should return updated location', async () => { - delete process.env['SHOULD_SYNC_ADOPTIONS']; const adoptedProbes = new AdoptedProbes(sqlStub, getProbesWithAdminData); sql.select.resolves([{ ...defaultAdoptedProbe, @@ -803,7 +807,6 @@ describe('AdoptedProbes', () => { }); it('getUpdatedLocation method should return null if connected.country !== adopted.countryOfCustomCity', async () => { - delete process.env['SHOULD_SYNC_ADOPTIONS']; const adoptedProbes = new AdoptedProbes(sqlStub, getProbesWithAdminData); sql.select.resolves([{ ...defaultAdoptedProbe, @@ -821,7 +824,6 @@ describe('AdoptedProbes', () => { }); it('getUpdatedLocation method should return null if "isCustomCity: false"', async () => { - delete process.env['SHOULD_SYNC_ADOPTIONS']; const adoptedProbes = new AdoptedProbes(sqlStub, getProbesWithAdminData); sql.select.resolves([{ ...defaultAdoptedProbe, @@ -836,8 +838,16 @@ describe('AdoptedProbes', () => { expect(updatedLocation).to.equal(null); }); - it('getUpdatedTags method should return updated tags', async () => { - delete process.env['SHOULD_SYNC_ADOPTIONS']; + it('getUpdatedTags method should return same tags array', async () => { + const adoptedProbes = new AdoptedProbes(sqlStub, getProbesWithAdminData); + sql.select.resolves([{ ...defaultAdoptedProbe, tags: '[]' }]); + + await adoptedProbes.syncDashboardData(); + const updatedTags = adoptedProbes.getUpdatedTags(defaultConnectedProbe); + expect(updatedTags).to.equal(defaultConnectedProbe.tags); + }); + + it('getUpdatedTags method should return user tags', async () => { const adoptedProbes = new AdoptedProbes(sqlStub, getProbesWithAdminData); await adoptedProbes.syncDashboardData(); @@ -848,13 +858,15 @@ describe('AdoptedProbes', () => { ]); }); - it('getUpdatedTags method should return same tags array if user tags are empty', async () => { - delete process.env['SHOULD_SYNC_ADOPTIONS']; + it('getUpdatedTags method should include user tag if public_probes: true', async () => { const adoptedProbes = new AdoptedProbes(sqlStub, getProbesWithAdminData); - sql.select.resolves([{ ...defaultAdoptedProbe, tags: '[]' }]); + sql.select.resolves([{ ...defaultAdoptedProbe, tags: '[]', publicProbes: 1 }]); await adoptedProbes.syncDashboardData(); const updatedTags = adoptedProbes.getUpdatedTags(defaultConnectedProbe); - expect(updatedTags).to.equal(defaultConnectedProbe.tags); + expect(updatedTags).to.deep.equal([ + { type: 'system', value: 'datacenter-network' }, + { type: 'user', value: 'u-jimaek' }, + ]); }); });