Adding initial team support.

This commit is contained in:
Sergiotarxz 2024-01-13 01:17:57 +01:00
parent c38474614d
commit 9279e6388a
14 changed files with 426 additions and 91 deletions

View File

@ -33,6 +33,7 @@ my $build = Module::Build->new(
'Crypt::Bcrypt' => 0,
'DBIx::Class::TimeStamp' => 0,
'DateTime::Format::HTTP' => 0,
'GIS::Distance' => 0,
},
);
$build->create_build_script;

View File

@ -238,37 +238,46 @@ export default class Conquer {
}
}
private getNearbyNodes(): void {
private async getNearbyNodes(): Promise<void> {
const urlNodes = new URL('/conquer/node/near', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
fetch(urlNodes).then(async (response) => {
let responseBody;
try {
responseBody = await response.json();
} catch (error) {
console.error('Error parseando json: ' + responseBody);
console.error(error);
return;
let response;
try {
response = await fetch(urlNodes);
} catch (error) {
console.error(error);
return;
}
let responseBody;
try {
responseBody = await response.json();
} catch (error) {
console.error('Error parseando json: ' + responseBody);
console.error(error);
return;
}
if (response.status !== 200) {
console.error(responseBody.error);
return;
}
const serverNodes: Record<string, MapNode> = {};
const nodes = JsonSerializer.deserialize(responseBody, MapNode);
if (!(nodes instanceof Array)) {
console.error('Received null instead of node list.');
return;
}
for (const node of nodes) {
if (!(node instanceof MapNode)) {
console.error('Received node is not a MapNode.');
continue;
}
if (response.status !== 200) {
console.error(responseBody.error);
return;
}
const serverNodes: Record<string, MapNode> = {};
const nodes = JsonSerializer.deserialize(responseBody, MapNode);
if (!(nodes instanceof Array)) {
console.error('Received null instead of node list.');
return;
}
for (const node of nodes) {
if (!(node instanceof MapNode)) {
console.error('Received node is not a MapNode.');
continue;
}
serverNodes[node.getId()] = node;
}
this.serverNodes = serverNodes;
this.refreshLayers();
});
node.on('update-nodes', async () => {
await this.sendCoordinatesToServer();
this.getNearbyNodes();
});
serverNodes[node.getId()] = node;
}
this.serverNodes = serverNodes;
this.refreshLayers();
}
private createIntervalPollNearbyNodes(): void {
@ -284,29 +293,31 @@ export default class Conquer {
}, 40000);
}
private sendCoordinatesToServer(): void {
private async sendCoordinatesToServer(): Promise<void> {
const urlLog = new URL('/conquer/user/coordinates', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
fetch(urlLog, {
method: 'POST',
body: JSON.stringify([
this.coordinate_1,
this.coordinate_2,
]),
}).then(async (res) => {
let responseBody;
try {
responseBody = await res.json();
} catch(error) {
console.error('Error parseando json: ' + responseBody);
console.error(error);
return;
}
if (res.status !== 200) {
console.error(responseBody.error);
}
}).catch((error) => {
let res;
try {
res = await fetch(urlLog, {
method: 'POST',
body: JSON.stringify([
this.coordinate_1,
this.coordinate_2,
])});
} catch (error) {
console.error(error)
});
return;
}
let responseBody;
try {
responseBody = await res.json();
} catch(error) {
console.error('Error parseando json: ' + responseBody);
console.error(error);
return;
}
if (res.status !== 200) {
console.error(responseBody.error);
}
}
private runPreStartState(): void {

View File

@ -24,12 +24,22 @@ export default class NodeView extends AbstractTopBarInterface {
super()
this.node = node;
}
public run() {
public async run() {
const mainNode = this.getMainNode()
this.runCallbacks('update-nodes');
try {
this.node = await this.node.fetch();
} catch (error) {
this.runCallbacks('close');
}
const view = this.getNodeFromTemplateId('conquer-view-node-template')
mainNode.append(view)
this.getNodeNameH2().innerText = this.node.getName();
this.getNodeDescriptionParagraph().innerText = this.node.getDescription();
this.getNodeDescriptionParagraph().innerText = this.node.getDescription()
+ "\n"
+ (this.node.isNear()
? 'Estas cerca y puedes interactuar con este sitio.'
: 'Estás demasiado lejos para hacer nada aquí.');
view.classList.remove('conquer-display-none')
mainNode.classList.remove('conquer-display-none')
}

View File

@ -7,6 +7,7 @@ import Fill from 'ol/style/Fill'
import Stroke from 'ol/style/Stroke'
import InterfaceManager from '@burguillosinfo/conquer/interface-manager'
import NodeView from '@burguillosinfo/conquer/interface/node-view'
import JsonSerializer from '@burguillosinfo/conquer/serializer';
@JsonObject()
export default class MapNode {
@ -21,14 +22,42 @@ export default class MapNode {
@JsonProperty() private name: string,
@JsonProperty() private description: string,
@JsonProperty() private kind: string,
@JsonProperty() private is_near: boolean,
) {
}
public async fetch(): Promise<MapNode> {
const urlNode = new URL('/conquer/node/' + this.uuid, window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
const response = await fetch(urlNode);
let responseBody;
const errorThrow = new Error('Unable to fetch node updated.');
try {
responseBody = await response.json();
} catch (error) {
console.error('Error parseando json: ' + responseBody);
console.error(error);
throw errorThrow;
}
if (response.status !== 200) {
console.error(responseBody.error);
throw errorThrow;
}
const node = JsonSerializer.deserialize(responseBody, MapNode);
if (!(node instanceof MapNode)) {
console.error('Unexpected JSON value for MapNode.');
throw errorThrow;
}
return node;
}
public click(interfaceManager: InterfaceManager): void {
const viewNodeInterface = new NodeView(this);
viewNodeInterface.on('close', () => {
interfaceManager.remove(viewNodeInterface);
});
viewNodeInterface.on('update-nodes', () => {
this.runCallbacks('update-nodes');
});
interfaceManager.push(viewNodeInterface);
this.runCallbacks('click');
}
@ -53,6 +82,10 @@ export default class MapNode {
public getType(): string {
return this.type;
}
public isNear(): boolean {
return this.is_near;
}
public getName(): string {
return this.name;

71
js-src/conquer/team.ts Normal file
View File

@ -0,0 +1,71 @@
import JsonSerializer from '@burguillosinfo/conquer/serializer';
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
@JsonObject()
export default class ConquerTeam {
@JsonProperty()
private kind: string;
@JsonProperty()
private uuid: string;
@JsonProperty()
private name: string;
@JsonProperty()
private description: string;
@JsonProperty()
private points: number;
@JsonProperty()
private color: string;
constructor(uuid: string, name: string, description: string, points: number, color: string) {
this.kind = 'ConquerTeam';
this.uuid = uuid;
this.name = name;
this.description = description;
this.points = points;
this.color = color;
}
public static async getTeam(uuid: string): Promise<ConquerTeam> {
const urlTeam = new URL('/conquer/team/' + uuid, window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
try {
const response = await fetch(urlTeam)
if (response.status !== 200) {
throw new Error('Invalid response fetching team.')
}
const teamData = await response.json()
let team = JsonSerializer.deserialize(teamData, ConquerTeam);
if (team === undefined) {
team = null;
}
if (!(team instanceof ConquerTeam)) {
throw new Error('Unable to parse team.');
}
return team;
} catch (error) {
console.error(error)
throw new Error('Unable to fetch Team.');
}
}
public static async getSelfTeam(): Promise<ConquerTeam | null> {
const urlTeam = new URL('/conquer/user/team', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
try {
const response = await fetch(urlTeam)
if (response.status !== 200) {
throw new Error('Invalid response fetching team.')
}
const teamData = await response.json()
let team = JsonSerializer.deserialize(teamData, ConquerTeam);
if (team === undefined) {
team = null;
}
if (team !== null && !(team instanceof ConquerTeam)) {
throw new Error('Unable to parse team.');
}
return team;
} catch (error) {
console.error(error)
throw new Error('Unable to fetch Team.');
}
}
}

View File

@ -1,3 +1,7 @@
import JsonSerializer from '@burguillosinfo/conquer/serializer';
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
import ConquerTeam from '@burguillosinfo/conquer/team';
export interface UserData {
is_admin: number
kind: string
@ -7,29 +11,37 @@ export interface UserData {
uuid: string
}
@JsonObject()
export default class ConquerUser {
private _isAdmin = false
private kind = "ConquerUser"
private lastActivity: string | null = null
private registrationDate: string | null = null
private username: string | null = null
private uuid: string | null = null
@JsonProperty()
private is_admin: boolean;
@JsonProperty()
private kind: string;
@JsonProperty()
private last_activity: string | null;
@JsonProperty()
private registration_date: string | null;
@JsonProperty()
private username: string;
@JsonProperty()
private uuid: string;
@JsonProperty({name: 'team'})
private team_uuid: string | null;
constructor(data: UserData) {
this.lastActivity = data.last_activity ?? null;
this.registrationDate = data.registration_date ?? null;
if (this.kind !== data.kind) {
throw new Error(`We cannot instance a user from a kind different to ${this.kind}.`)
}
this._isAdmin = !!data.is_admin || false
this.uuid = data.uuid
this.username = data.username
if (this.username === null || this.username === undefined) {
throw new Error('No username in user instance')
}
if (this.uuid === null || this.username === undefined) {
throw new Error('No uuid in user instance')
constructor(kind: string, uuid: string, username: string, is_admin = false, registration_date: string | null = null, last_activity: string | null = null) {
this.kind = kind;
this.uuid = uuid;
this.username = username;
this.is_admin = is_admin;
this.registration_date = registration_date;
this.last_activity = last_activity;
}
public async getTeam(): Promise<ConquerTeam | null> {
if (this.team_uuid === null) {
return null;
}
return ConquerTeam.getTeam(this.team_uuid);
}
public static async getSelfUser(): Promise<ConquerUser | null> {
@ -40,7 +52,11 @@ export default class ConquerUser {
throw new Error('Invalid response fetching user.')
}
const userData = await response.json()
return new ConquerUser(userData)
const user = JsonSerializer.deserialize(userData, ConquerUser);
if (!(user instanceof ConquerUser)) {
throw new Error('Unable to parse user.');
}
return user;
} catch (error) {
console.error(error)
return null
@ -53,6 +69,6 @@ export default class ConquerUser {
return this.username
}
public isAdmin(): boolean {
return this._isAdmin
return this.is_admin
}
}

View File

@ -7,6 +7,7 @@ use Mojo::Base 'Mojolicious', -signatures;
# This method will run once at server start
sub startup ($self) {
my $metrics = BurguillosInfo::Controller::Metrics->new;
$self->sessions->default_expiration(0);
$self->hook(
around_dispatch => sub {
my $next = shift;
@ -91,9 +92,12 @@ sub startup ($self) {
$r->get('/stats')->to('Metrics#stats');
$r->get('/conquer')->to('Conquer#index');
$r->put('/conquer/user')->to('UserConquer#create');
$r->get('/conquer/user/team')->to('UserConquer#getSelfTeam');
$r->post('/conquer/user/coordinates')->to('UserConquer#setCoordinates');
$r->get('/conquer/team/<uuid>')->to('ConquerTeam#get');
$r->put('/conquer/node')->to('ConquerNode#create');
$r->get('/conquer/node/near')->to('ConquerNode#nearbyNodes');
$r->get('/conquer/node/<uuid>')->to('ConquerNode#get');
$r->get('/conquer/user')->to('UserConquer#get_self');
$r->post('/conquer/user/login')->to('UserConquer#login');
$r->get('/conquer/tile/<zoom>/<x>/<y>.png')->to('ConquerTile#tile');

View File

@ -9,7 +9,30 @@ use utf8;
use Mojo::Base 'Mojolicious::Controller', '-signatures';
use UUID::URandom qw/create_uuid_string/;
use BurguillosInfo::Schema;
sub get($self) {
my $uuid = $self->param('uuid');
my $user = $self->current_user;
if (!defined $uuid || !$uuid) {
return $self->render(status => 400, json => {
error => 'UUID de nodo invalido.',
});
}
my $schema = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
my @nodes = $schema->search({uuid => $uuid});
if (!scalar @nodes) {
return $self->render(status => 404, json => {
error => 'Nodo no encontrado',
});
}
my $node = $nodes[0];
if (defined $user) {
return $self->render(json => $node->serialize($user));
}
return $self->render(json => $node->serialize());
}
sub create ($self) {
my $user = $self->current_user;
if ( !defined $user ) {
@ -118,7 +141,7 @@ sub nearbyNodes($self) {
});
}
my @nodes = BurguillosInfo::Schema->Schema->resultset('ConquerNode')->search({});
@nodes = map { $_->serialize } @nodes;
@nodes = map { $_->serialize($user) } @nodes;
return $self->render(json => \@nodes);
}

View File

@ -0,0 +1,57 @@
package BurguillosInfo::Controller::ConquerTeam;
use v5.34.1;
use strict;
use warnings;
use utf8;
use Mojo::Base 'Mojolicious::Controller', '-signatures';
use UUID::URandom qw/create_uuid_string/;
use JSON;
use BurguillosInfo::Schema;
sub get($self) {
my $user = $self->current_user;
if (!defined $user) {
return $self->render(status => 401, json => {
error => 'You must be logged to fetch a team.',
});
}
my $uuid = $self->param('uuid');
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
my @teams = $resultset->search({
'uuid' => $uuid,
});
if (scalar @teams <= 0) {
return $self->render( status => 404, json => {
error => 'This team does not exist.',
});
}
my $team = $teams[0];
return $self->render(json => $team);
}
sub getSelfTeam($self) {
my $user = $self->current_user;
if (!defined $user) {
return $self->render(status => 401, json => {
error => 'You must be logged to fetch your Team.',
});
}
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
my @teams = $resultset->search({
'players.uuid' => $user->uuid
}, {
join => 'players',
});
if (scalar @teams <= 0) {
return $self->render(json => undef);
}
my $team = $teams[0];
return $self->render(json => $team);
}
1;

View File

@ -28,11 +28,11 @@ sub MIGRATIONS {
path TEXT,
FOREIGN KEY (path) REFERENCES paths(path)
)',
'ALTER TABLE paths ADD column last_seen TIMESTAMP;',
'ALTER TABLE paths ADD COLUMN last_seen TIMESTAMP;',
'ALTER TABLE paths ALTER COLUMN last_seen SET DEFAULT NOW();',
'ALTER TABLE requests ADD PRIMARY KEY (uuid)',
'CREATE INDEX request_extra_index on requests (date, path);',
'ALTER TABLE requests ADD column referer text;',
'ALTER TABLE requests ADD COLUMN referer text;',
'CREATE INDEX request_referer_index on requests (referer);',
'ALTER TABLE requests ADD COLUMN country TEXT;',
'CREATE INDEX request_country_index on requests (country);',
@ -71,6 +71,17 @@ sub MIGRATIONS {
'ALTER TABLE conquer_user ALTER COLUMN last_coordinate_1 DROP DEFAULT;',
'ALTER TABLE conquer_user ADD COLUMN last_coordinate_2 REAL NOT NULL DEFAULT 0;',
'ALTER TABLE conquer_user ALTER COLUMN last_coordinate_2 DROP DEFAULT;',
'CREATE TABLE conquer_teams (
uuid UUID NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT \'\',
points INTEGER NOT NULL DEFAULT 0
);',
'ALTER TABLE conquer_user ADD COLUMN team UUID REFERENCES conquer_teams (uuid);',
'ALTER TABLE conquer_node ADD COLUMN team UUID REFERENCES conquer_teams (uuid);',
'ALTER TABLE conquer_teams ADD COLUMN color TEXT NOT NULL DEFAULT \'#000\';',
'ALTER TABLE conquer_teams ALTER COLUMN color SET DEFAULT \'#555\';',
'ALTER TABLE conquer_teams ALTER COLUMN color SET DEFAULT \'#aaa\';',
);
}

View File

@ -9,6 +9,9 @@ use parent 'DBIx::Class::Core';
use feature 'signatures';
use JSON;
use GIS::Distance;
__PACKAGE__->table('conquer_node');
__PACKAGE__->load_components("TimeStamp");
@ -40,9 +43,9 @@ __PACKAGE__->add_columns(
}
);
sub serialize ($self) {
sub serialize ( $self, $player = undef ) {
$self = $self->get_from_storage();
return {
my $return = {
kind => 'ConquerNode',
uuid => $self->uuid,
name => $self->name,
@ -50,7 +53,31 @@ sub serialize ($self) {
type => $self->type,
coordinate_1 => $self->coordinate_1,
coordinate_2 => $self->coordinate_2,
is_near => $self->is_near($player),
};
return $return;
}
sub is_near ( $self, $player ) {
if ( !defined $player ) {
return $JSON::false;
}
# Meters
if ($self->get_distance_to_player($player) < 100) {
return $JSON::true;
}
return $JSON::false;
}
sub get_distance_to_player ($self, $player) {
my $longitude_player = $player->last_coordinate_1;
my $latitude_player = $player->last_coordinate_2;
my $longitude_node = $self->coordinate_1;
my $latitude_node = $self->coordinate_2;
my $gis = GIS::Distance->new;
# Setting distance to meters.
my $distance = $gis->distance_metal( $latitude_node, $longitude_node, $latitude_player, $longitude_player) * 1000;
}
__PACKAGE__->set_primary_key('uuid');
1;

View File

@ -0,0 +1,53 @@
package BurguillosInfo::Schema::Result::ConquerTeam;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
use feature 'signatures';
use JSON;
__PACKAGE__->table('conquer_teams');
__PACKAGE__->load_components("TimeStamp");
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
is_nullable => 0,
},
name => {
data_type => 'text',
is_nullable => 0,
},
description => {
data_type => 'text',
is_nullable => 0,
},
points => {
data_type => 'integer',
is_nullable => 0,
},
color => {
data_type => 'text',
is_nullable => 0,
},
);
sub serialize ($self) {
$self = $self->get_from_storage();
return {
kind => 'ConquerTeam',
uuid => $self->uuid,
name => $self->name,
description => $self->description,
points => $self->points,
color => $self->color,
};
}
__PACKAGE__->has_many( players => 'BurguillosInfo::Schema::Result::ConquerUser', 'team');
__PACKAGE__->set_primary_key('uuid');
1;

View File

@ -9,6 +9,8 @@ use parent 'DBIx::Class::Core';
use feature 'signatures';
use JSON;
__PACKAGE__->table('conquer_user');
__PACKAGE__->load_components("TimeStamp");
@ -17,6 +19,10 @@ __PACKAGE__->add_columns(
data_type => 'uuid',
is_nullable => 0,
},
team => {
data_type => 'uuid',
is_nullable => 1,
},
username => {
data_type => 'text',
is_nullable => 0,
@ -52,16 +58,16 @@ __PACKAGE__->add_columns(
},
);
sub coordinates($self, $coordinates = undef) {
if (defined $coordinates) {
if (ref $coordinates ne 'ARRAY' || scalar $coordinates->@* != 2) {
sub coordinates ( $self, $coordinates = undef ) {
if ( defined $coordinates ) {
if ( ref $coordinates ne 'ARRAY' || scalar $coordinates->@* != 2 ) {
die 'The second parameter of this subroutine '
. 'must be an ARRAYREF of exactly two elements.';
. 'must be an ARRAYREF of exactly two elements.';
}
$self->last_coordinate_1($coordinates->[0]);
$self->last_coordinate_2($coordinates->[1]);
$self->last_coordinate_1( $coordinates->[0] );
$self->last_coordinate_2( $coordinates->[1] );
}
return [$self->last_coordinate_1, $self->last_coordinate_2];
return [ $self->last_coordinate_1, $self->last_coordinate_2 ];
}
sub serialize_to_owner ($self) {
@ -69,8 +75,9 @@ sub serialize_to_owner ($self) {
return {
kind => 'ConquerUser',
uuid => $self->uuid,
team => $self->team,
username => $self->username,
is_admin => $self->is_admin,
is_admin => $self->is_admin ? $JSON::true : $JSON::false,
last_activity => $self->last_activity,
registration_date => $self->registration_date,
};

File diff suppressed because one or more lines are too long