Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mark user owned probes in API responses #607

Merged
merged 5 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions migrations/create-tables.js.sql
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -33,15 +34,15 @@ 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),
recipient CHAR(36),
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,
Expand Down Expand Up @@ -72,7 +73,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,
Expand All @@ -86,4 +87,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;
31 changes: 17 additions & 14 deletions src/lib/override/adopted-probes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -42,15 +43,18 @@ export type Adoption = {
longitude: number | null;
asn: number | null;
network: string | null;
githubUsername: string | null;
publicProbes: boolean;
}

type Row = Omit<Adoption, 'isCustomCity' | 'tags' | 'systemTags' | 'altIps' | 'isIPv4Supported' | 'isIPv6Supported'> & {
export type Row = Omit<Adoption, 'isCustomCity' | 'tags' | 'systemTags' | 'altIps' | 'isIPv4Supported' | 'isIPv6Supported' | 'publicProbes'> & {
altIps: string;
tags: string;
systemTags: string;
isCustomCity: number;
isIPv4Supported: number;
isIPv6Supported: number;
publicProbes: number;
}

type AdoptionFieldDescription = {
Expand Down Expand Up @@ -161,16 +165,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}`,
}] : []),
];
}

Expand All @@ -182,13 +190,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);
Expand Down Expand Up @@ -237,10 +238,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<Row[]>();
.orderByRaw(`${ADOPTIONS_TABLE}.lastSyncDate DESC, ${ADOPTIONS_TABLE}.onlineTimesToday DESC, FIELD(${ADOPTIONS_TABLE}.status, 'ready') DESC, ${ADOPTIONS_TABLE}.id DESC`)
.select<Row[]>(`${ADOPTIONS_TABLE}.*`, `${USERS_TABLE}.github_username AS githubUsername`, `${USERS_TABLE}.public_probes as publicProbes`);

const adoptions: Adoption[] = rows.map(row => ({
...row,
Expand All @@ -253,6 +255,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;
Expand Down
46 changes: 29 additions & 17 deletions test/tests/unit/override/adopted-probes.test.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -30,6 +30,8 @@ describe('AdoptedProbes', () => {
longitude: -6.25,
asn: 16509,
network: 'Amazon.com, Inc.',
githubUsername: 'jimaek',
publicProbes: 0,
};

const defaultConnectedProbe: Probe = {
Expand Down Expand Up @@ -87,6 +89,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;
Expand All @@ -102,6 +105,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);
Expand Down Expand Up @@ -414,13 +418,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).',
});

Expand Down Expand Up @@ -516,8 +520,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({
Expand Down Expand Up @@ -761,11 +765,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,
Expand Down Expand Up @@ -794,7 +798,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,
Expand All @@ -812,7 +815,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,
Expand All @@ -827,8 +829,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();
Expand All @@ -839,13 +849,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' },
]);
});
});
Loading