more strict types

This commit is contained in:
Robin Appelman 2025-04-14 20:46:06 +02:00
commit 3b5d3f2efc
13 changed files with 223 additions and 115 deletions

4
api-server/Cargo.lock generated
View file

@ -1964,7 +1964,7 @@ dependencies = [
[[package]]
name = "ugc-scraper"
version = "0.5.0"
source = "git+https://github.com/icewind1991/ugc-scaper#b290e2f83db161e2a7bcf4e199400226d56a2076"
source = "git+https://github.com/icewind1991/ugc-scaper#2d1565117d2b9e9933c0608f00809f6815420156"
dependencies = [
"regex",
"reqwest",
@ -1979,7 +1979,7 @@ dependencies = [
[[package]]
name = "ugc-scraper-types"
version = "0.2.0"
source = "git+https://github.com/icewind1991/ugc-scaper#b290e2f83db161e2a7bcf4e199400226d56a2076"
source = "git+https://github.com/icewind1991/ugc-scaper#2d1565117d2b9e9933c0608f00809f6815420156"
dependencies = [
"serde",
"steamid-ng",

View file

@ -1,4 +1,4 @@
CREATE TABLE matches
CREATE TABLE IF NOT EXISTS matches
(
id INTEGER NOT NULL,
team_home INTEGER NOT NULL,

View file

@ -1,19 +1,81 @@
CREATE TYPE region AS ENUM ('europe', 'north-america', 'south-america', 'asia', 'australia');
CREATE TYPE game_mode AS ENUM ('highlander', 'sixes', 'fours', 'ultiduo');
CREATE TYPE game_mode AS ENUM ('highlander', 'eights', 'sixes', 'fours', 'ultiduo');
CREATE TYPE membership_role AS ENUM ('leader', 'member');
CREATE TABLE teams
(
id INTEGER NOT NULL,
name VARCHAR NOT NULL,
image VARCHAR NOT NULL,
form
format game_mode NOT NULL,
region region,
timezone VARCHAR NOT NULL,
steam_group VARCHAR,
division VARCHAR NOT NULL,
description VARCHAR NOT NULL
);
CREATE UNIQUE INDEX matches_id_idx
ON matches USING BTREE (id);
CREATE UNIQUE INDEX teams_id_idx
ON teams USING BTREE (id);
CREATE INDEX matches_home_idx
ON matches USING BTREE (team_home);
CREATE INDEX teams_format_idx
ON teams USING BTREE (format);
CREATE INDEX matches_away_idx
ON matches USING BTREE (team_away);
CREATE INDEX teams_region_idx
ON teams USING BTREE (region);
CREATE INDEX teams_division_idx
ON teams USING BTREE (division);
CREATE INDEX teams_timezone_idx
ON teams USING BTREE (timezone);
CREATE TABLE titles
(
team_id INTEGER NOT NULL,
title VARCHAR NOT NULL
);
CREATE INDEX titles_team_id_idx
ON titles USING BTREE (team_id);
CREATE TABLE name_changes
(
team_id INTEGER NOT NULL,
from_tag VARCHAR NOT NULL,
from_name VARCHAR NOT NULL,
to_tag VARCHAR NOT NULL,
to_name VARCHAR NOT NULL,
date DATE
);
CREATE INDEX name_changes_team_id_idx
ON name_changes USING BTREE (team_id);
CREATE TABLE records
(
team_id INTEGER NOT NULL,
season INTEGER NOT NULL,
wins INTEGER NOT NULL,
losses INTEGER NOT NULL
);
CREATE INDEX records_team_id_idx
ON records USING BTREE (team_id);
CREATE INDEX records_season_idx
ON records USING BTREE (season);
CREATE TABLE memberships
(
team_id INTEGER NOT NULL,
steam_id BIGINT NOT NULL,
role membership_role NOT NULL,
since TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE INDEX memberships_team_id_idx
ON memberships USING BTREE (team_id);
CREATE INDEX memberships_steam_id_idx
ON memberships USING BTREE (steam_id);

View file

@ -30,7 +30,7 @@ rustPlatform.buildRustPackage rec {
cargoLock = {
lockFile = ./api-server/Cargo.lock;
outputHashes = {
"ugc-scraper-0.5.0" = "sha256-akmSE/AwxsBhF+SiUQWV2oTzlNi4s61AztGShYMohr4=";
"ugc-scraper-0.5.0" = "sha256-umMGHYKSSor5Co7eIb0lDpEUWS/qFscX7TpDGMXecRo=";
};
};
}

View file

@ -9,7 +9,7 @@ use scraper::{Html, Selector};
use std::str::FromStr;
use std::sync::OnceLock;
use time::{Date, PrimitiveDateTime, Time, UtcOffset};
use ugc_scraper_types::{GameMode, Region};
use ugc_scraper_types::{GameMode, MembershipRole, Region};
const SELECTOR_TEAM_NAME: &str = ".container .col-md-12 h1 > b";
const SELECTOR_TEAM_TAG: &str = ".container .col-md-12 h1 > span";
@ -274,14 +274,19 @@ impl Parser for TeamParser {
})?
.split('\n')
.next()
.unwrap();
.unwrap()
.parse::<MembershipRole>()
.map_err(|err| ParseError::InvalidText {
role: "member role",
text: err.text,
})?;
let since = select_text(row, &self.selector_team_member_since).ok_or(
ParseError::ElementNotFound {
selector: SELECTOR_TEAM_MEMBER_SINCE,
role: "team member since",
},
)?;
let role = role.trim().to_string();
let since = whitespace_regex.replace_all(since.trim(), " ");
let since = if since.starts_with('(') {
let part = since

View file

@ -17,37 +17,37 @@ expression: parsed
{
"name": "uncle spoodg[A]",
"steam_id": "76561198014928031",
"role": "Leader",
"role": "leader",
"since": "+002012-01-29T00:00:00.000000000Z"
},
{
"name": "CratZ crayonnaise",
"steam_id": "76561198021136779",
"role": "Member",
"role": "member",
"since": "+002012-01-30T00:00:00.000000000Z"
},
{
"name": "calm``",
"steam_id": "76561197968127159",
"role": "Member",
"role": "member",
"since": "+002012-01-30T00:00:00.000000000Z"
},
{
"name": "Plysse",
"steam_id": "76561198028175255",
"role": "Member",
"role": "member",
"since": "+002012-05-30T00:00:00.000000000Z"
},
{
"name": "calm``",
"steam_id": "76561197968127159",
"role": "Member",
"role": "member",
"since": "+002012-05-30T00:00:00.000000000Z"
},
{
"name": "pwalt",
"steam_id": "76561197988404333",
"role": "Member",
"role": "member",
"since": "+002012-09-14T00:00:00.000000000Z"
}
],

View file

@ -20,91 +20,91 @@ expression: parsed
{
"name": "GCKimo",
"steam_id": "76561197992327511",
"role": "Leader",
"role": "leader",
"since": "+002013-05-07T09:31:00.000000000-05:00"
},
{
"name": "Gielewiel9",
"steam_id": "76561198061174419",
"role": "Member",
"role": "member",
"since": "+002013-09-11T01:21:00.000000000-05:00"
},
{
"name": "SUZY Sacrénom d'un",
"steam_id": "76561198004734774",
"role": "Member",
"role": "member",
"since": "+002013-10-03T01:45:00.000000000-05:00"
},
{
"name": "Vipe",
"steam_id": "76561198059011634",
"role": "Member",
"role": "member",
"since": "+002014-05-21T03:32:00.000000000-05:00"
},
{
"name": "spreijer tf2lt",
"steam_id": "76561198032234067",
"role": "Member",
"role": "member",
"since": "+002014-06-04T01:06:00.000000000-05:00"
},
{
"name": "Herpa",
"steam_id": "76561198183437643",
"role": "Member",
"role": "member",
"since": "+002016-06-21T03:05:00.000000000-05:00"
},
{
"name": "Icewind demostf",
"steam_id": "76561198024494988",
"role": "Member",
"role": "member",
"since": "+002017-02-21T03:52:00.000000000-05:00"
},
{
"name": "Vclox",
"steam_id": "76561198056783619",
"role": "Member",
"role": "member",
"since": "+002018-02-28T02:29:00.000000000-05:00"
},
{
"name": "Fish",
"steam_id": "76561198052362074",
"role": "Member",
"role": "member",
"since": "+002018-06-20T02:12:00.000000000-05:00"
},
{
"name": "Bobbert",
"steam_id": "76561198071877015",
"role": "Member",
"role": "member",
"since": "+002019-06-19T01:59:00.000000000-05:00"
},
{
"name": "Kaga",
"steam_id": "76561198040965137",
"role": "Member",
"role": "member",
"since": "+002020-02-11T06:35:00.000000000-05:00"
},
{
"name": "GMsU CreepsiliusM",
"steam_id": "76561198071903356",
"role": "Member",
"role": "member",
"since": "+002022-10-11T04:43:00.000000000-05:00"
},
{
"name": "DelT",
"steam_id": "76561198204007537",
"role": "Member",
"role": "member",
"since": "+002023-03-01T03:02:00.000000000-05:00"
},
{
"name": "Deity",
"steam_id": "76561198076020012",
"role": "Member",
"role": "member",
"since": "+002023-08-16T04:31:00.000000000-05:00"
},
{
"name": "Ikaros",
"steam_id": "76561198158482651",
"role": "Member",
"role": "member",
"since": "+002023-08-16T04:32:00.000000000-05:00"
}
],

View file

@ -21,91 +21,91 @@ expression: parsed
{
"name": "Kikuro",
"steam_id": "76561198201593238",
"role": "Leader",
"role": "leader",
"since": "+002022-01-10T12:43:00.000000000-05:00"
},
{
"name": "Mr Ketchum",
"steam_id": "76561198274737917",
"role": "Member",
"role": "member",
"since": "+002022-09-25T10:41:00.000000000-05:00"
},
{
"name": "momichi gangster2",
"steam_id": "76561198195717911",
"role": "Member",
"role": "member",
"since": "+002022-10-24T04:47:00.000000000-05:00"
},
{
"name": "Lord Seith",
"steam_id": "76561198190100828",
"role": "Member",
"role": "member",
"since": "+002022-11-20T11:02:00.000000000-05:00"
},
{
"name": "gasparmx",
"steam_id": "76561197998611994",
"role": "Member",
"role": "member",
"since": "+002023-01-31T02:31:00.000000000-05:00"
},
{
"name": "Gabriel",
"steam_id": "76561198151816133",
"role": "Member",
"role": "member",
"since": "+002023-02-11T10:11:00.000000000-05:00"
},
{
"name": "Tnaas",
"steam_id": "76561198284886594",
"role": "Member",
"role": "member",
"since": "+002023-02-28T11:05:00.000000000-05:00"
},
{
"name": "true",
"steam_id": "76561198153117396",
"role": "Member",
"role": "member",
"since": "+002023-03-05T02:02:00.000000000-05:00"
},
{
"name": "Galleto",
"steam_id": "76561198280047870",
"role": "Member",
"role": "member",
"since": "+002023-03-10T10:52:00.000000000-05:00"
},
{
"name": "Syn",
"steam_id": "76561199100303067",
"role": "Member",
"role": "member",
"since": "+002023-03-10T10:57:00.000000000-05:00"
},
{
"name": "Neko",
"steam_id": "76561198414358056",
"role": "Member",
"role": "member",
"since": "+002023-03-13T03:07:00.000000000-05:00"
},
{
"name": "jasperr",
"steam_id": "76561199106104716",
"role": "Member",
"role": "member",
"since": "+002023-03-19T02:09:00.000000000-05:00"
},
{
"name": "on99",
"steam_id": "76561198164610169",
"role": "Member",
"role": "member",
"since": "+002023-03-19T02:09:00.000000000-05:00"
},
{
"name": "tonyyyy",
"steam_id": "76561198305808710",
"role": "Member",
"role": "member",
"since": "+002023-03-21T05:20:00.000000000-05:00"
},
{
"name": "Michaelpc1",
"steam_id": "76561198088365519",
"role": "Member",
"role": "member",
"since": "+002023-05-04T08:58:00.000000000-05:00"
}
],

View file

@ -19,127 +19,127 @@ expression: parsed
{
"name": "Icewind demostf",
"steam_id": "76561198024494988",
"role": "Leader",
"role": "leader",
"since": "+002013-08-09T03:23:00.000000000-05:00"
},
{
"name": "Fish",
"steam_id": "76561198052362074",
"role": "Leader",
"role": "leader",
"since": "+002014-04-30T10:37:00.000000000-05:00"
},
{
"name": "GCKimo",
"steam_id": "76561197992327511",
"role": "Leader",
"role": "leader",
"since": "+002016-06-19T11:24:00.000000000-05:00"
},
{
"name": "NoSocks",
"steam_id": "76561198012110404",
"role": "Member",
"role": "member",
"since": "+002013-08-06T05:21:00.000000000-05:00"
},
{
"name": "Shoosh",
"steam_id": "76561198049593717",
"role": "Member",
"role": "member",
"since": "+002014-09-12T09:37:00.000000000-05:00"
},
{
"name": "Dirty Sneeds Done",
"steam_id": "76561198049312442",
"role": "Member",
"role": "member",
"since": "+002015-09-23T12:24:00.000000000-05:00"
},
{
"name": "Deity",
"steam_id": "76561198076020012",
"role": "Member",
"role": "member",
"since": "+002015-12-29T02:52:00.000000000-05:00"
},
{
"name": "jojo",
"steam_id": "76561197995029224",
"role": "Member",
"role": "member",
"since": "+002016-01-17T10:47:00.000000000-05:00"
},
{
"name": "bigdog",
"steam_id": "76561198076014163",
"role": "Member",
"role": "member",
"since": "+002016-05-25T04:08:00.000000000-05:00"
},
{
"name": "musTard",
"steam_id": "76561197990486664",
"role": "Member",
"role": "member",
"since": "+002017-01-17T07:43:00.000000000-05:00"
},
{
"name": "Kaga",
"steam_id": "76561198040965137",
"role": "Member",
"role": "member",
"since": "+002018-11-08T06:42:00.000000000-05:00"
},
{
"name": "STEEEEEEEEEEELAZ",
"steam_id": "76561198036824480",
"role": "Member",
"role": "member",
"since": "+002019-09-29T08:35:00.000000000-05:00"
},
{
"name": "Derakusa",
"steam_id": "76561198011495003",
"role": "Member",
"role": "member",
"since": "+002020-10-01T04:28:00.000000000-05:00"
},
{
"name": "Kireek",
"steam_id": "76561198052694464",
"role": "Member",
"role": "member",
"since": "+002022-01-27T04:17:00.000000000-05:00"
},
{
"name": "Royal Flush",
"steam_id": "76561198052084714",
"role": "Member",
"role": "member",
"since": "+002022-05-08T06:42:00.000000000-05:00"
},
{
"name": "BaaBo",
"steam_id": "76561198004331478",
"role": "Member",
"role": "member",
"since": "+002023-01-09T10:18:00.000000000-05:00"
},
{
"name": "drew",
"steam_id": "76561198012304706",
"role": "Member",
"role": "member",
"since": "+002023-02-12T05:54:00.000000000-05:00"
},
{
"name": "Raipe",
"steam_id": "76561198061082936",
"role": "Member",
"role": "member",
"since": "+002023-03-19T05:27:00.000000000-05:00"
},
{
"name": "Teroantero2007",
"steam_id": "76561197996902035",
"role": "Member",
"role": "member",
"since": "+002023-06-26T02:01:00.000000000-05:00"
},
{
"name": "taskmast33r",
"steam_id": "76561198218881647",
"role": "Member",
"role": "member",
"since": "+002023-07-22T04:46:00.000000000-05:00"
},
{
"name": "marko",
"steam_id": "76561198274165935",
"role": "Member",
"role": "member",
"since": "+002023-10-19T01:13:00.000000000-05:00"
}
],

View file

@ -35,37 +35,37 @@ expression: parsed
{
"name": "Bonesaw",
"steam_id": "76561198008087350",
"role": "Leader",
"role": "leader",
"since": "+002014-04-17T11:16:00.000000000-05:00"
},
{
"name": "ether",
"steam_id": "76561198064711665",
"role": "Member",
"role": "member",
"since": "+002019-11-22T05:40:00.000000000-05:00"
},
{
"name": "eerie",
"steam_id": "76561198044268201",
"role": "Member",
"role": "member",
"since": "+002020-06-13T12:11:00.000000000-05:00"
},
{
"name": "bug",
"steam_id": "76561198078731479",
"role": "Member",
"role": "member",
"since": "+002020-10-09T07:29:00.000000000-05:00"
},
{
"name": "blank",
"steam_id": "76561198105529673",
"role": "Member",
"role": "member",
"since": "+002021-10-22T08:07:00.000000000-05:00"
},
{
"name": "JohhnyFromCali 7",
"steam_id": "76561198044015725",
"role": "Member",
"role": "member",
"since": "+002021-11-19T09:13:00.000000000-05:00"
}
],

View file

@ -17,55 +17,55 @@ expression: parsed
{
"name": "copycat",
"steam_id": "76561198072397106",
"role": "Leader",
"role": "leader",
"since": "+002019-01-22T11:04:00.000000000-05:00"
},
{
"name": "Mei",
"steam_id": "76561198134574357",
"role": "Member",
"role": "member",
"since": "+002019-01-22T11:07:00.000000000-05:00"
},
{
"name": "melstrom",
"steam_id": "76561198068890768",
"role": "Member",
"role": "member",
"since": "+002019-01-22T11:07:00.000000000-05:00"
},
{
"name": "Otter Speaking Eng",
"steam_id": "76561198073466450",
"role": "Member",
"role": "member",
"since": "+002019-01-22T11:13:00.000000000-05:00"
},
{
"name": "DarkSlayer415",
"steam_id": "76561198053913751",
"role": "Member",
"role": "member",
"since": "+002019-01-22T05:54:00.000000000-05:00"
},
{
"name": "corn face",
"steam_id": "76561198066113821",
"role": "Member",
"role": "member",
"since": "+002019-01-22T06:59:00.000000000-05:00"
},
{
"name": "erin",
"steam_id": "76561198307572837",
"role": "Member",
"role": "member",
"since": "+002019-01-22T07:17:00.000000000-05:00"
},
{
"name": "Naps",
"steam_id": "76561198061524698",
"role": "Member",
"role": "member",
"since": "+002019-01-23T04:16:00.000000000-05:00"
},
{
"name": "java",
"steam_id": "76561198158291013",
"role": "Member",
"role": "member",
"since": "+002019-01-23T08:52:00.000000000-05:00"
}
],

View file

@ -17,109 +17,109 @@ expression: parsed
{
"name": "rchl",
"steam_id": "76561198027925375",
"role": "Leader",
"role": "leader",
"since": "+002013-08-13T00:00:00.000000000Z"
},
{
"name": "Yollide",
"steam_id": "76561197964088385",
"role": "Leader",
"role": "leader",
"since": "+002013-08-13T00:00:00.000000000Z"
},
{
"name": "Gankmeister",
"steam_id": "76561198017648752",
"role": "Leader",
"role": "leader",
"since": "+002013-08-14T00:00:00.000000000Z"
},
{
"name": "Hng",
"steam_id": "76561198042955655",
"role": "Member",
"role": "member",
"since": "+002013-08-13T00:00:00.000000000Z"
},
{
"name": "snake kryptonite",
"steam_id": "76561197998673784",
"role": "Member",
"role": "member",
"since": "+002013-08-13T00:00:00.000000000Z"
},
{
"name": "Hugh",
"steam_id": "76561197968399860",
"role": "Member",
"role": "member",
"since": "+002013-08-13T00:00:00.000000000Z"
},
{
"name": "Joy",
"steam_id": "76561197997012073",
"role": "Member",
"role": "member",
"since": "+002013-08-13T00:00:00.000000000Z"
},
{
"name": "BIG RAT",
"steam_id": "76561197975762186",
"role": "Member",
"role": "member",
"since": "+002013-08-13T00:00:00.000000000Z"
},
{
"name": "GoldenBrownBear",
"steam_id": "76561197991937066",
"role": "Member",
"role": "member",
"since": "+002013-08-22T00:00:00.000000000Z"
},
{
"name": "bfl Satan",
"steam_id": "76561197996097401",
"role": "Member",
"role": "member",
"since": "+002013-08-27T00:00:00.000000000Z"
},
{
"name": "Murow",
"steam_id": "76561198046399702",
"role": "Member",
"role": "member",
"since": "+002013-08-31T00:00:00.000000000Z"
},
{
"name": "Ibby",
"steam_id": "76561198001095962",
"role": "Member",
"role": "member",
"since": "+002013-09-05T00:00:00.000000000Z"
},
{
"name": "mpb",
"steam_id": "76561197995307557",
"role": "Member",
"role": "member",
"since": "+002013-09-09T00:00:00.000000000Z"
},
{
"name": "Hei",
"steam_id": "76561197987102243",
"role": "Member",
"role": "member",
"since": "+002013-09-15T00:00:00.000000000Z"
},
{
"name": "to go to bed forev",
"steam_id": "76561198025368981",
"role": "Member",
"role": "member",
"since": "+002013-09-15T00:00:00.000000000Z"
},
{
"name": "stanplsStochast1c",
"steam_id": "76561197995209440",
"role": "Member",
"role": "member",
"since": "+002013-09-28T00:00:00.000000000Z"
},
{
"name": "ChinGoo",
"steam_id": "76561197990116981",
"role": "Member",
"role": "member",
"since": "+002013-10-03T00:00:00.000000000Z"
},
{
"name": "Bruce Leeroy",
"steam_id": "76561198010708247",
"role": "Member",
"role": "member",
"since": "+002013-10-07T00:00:00.000000000Z"
}
],

View file

@ -1,5 +1,4 @@
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Display;
use std::str::FromStr;
pub use steamid_ng::SteamID;
@ -175,11 +174,50 @@ pub struct Membership {
pub name: String,
#[cfg_attr(feature = "serde", serde(with = "serde_steam_id_as_string"))]
pub steam_id: SteamID,
pub role: String,
pub role: MembershipRole,
#[cfg_attr(feature = "serde", serde(with = "time::serde::iso8601"))]
pub since: OffsetDateTime,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum MembershipRole {
Leader,
Member,
}
#[derive(Debug, Clone, Error)]
#[error("Invalid membership role {text}")]
pub struct InvalidMembershipRole {
pub text: String,
}
impl FromStr for MembershipRole {
type Err = InvalidMembershipRole;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim() {
"leader" => Ok(MembershipRole::Leader),
"member" => Ok(MembershipRole::Member),
"Leader" => Ok(MembershipRole::Leader),
"Member" => Ok(MembershipRole::Member),
_ => Err(InvalidMembershipRole { text: s.into() }),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for MembershipRole {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
Self::from_str(s).map_err(D::Error::custom)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Record {
@ -331,19 +369,21 @@ impl GameMode {
}
}
impl Serialize for GameMode {
#[cfg(feature = "serde")]
impl serde::Serialize for GameMode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for GameMode {
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for GameMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
D: serde::Deserializer<'de>,
{
let s = <&str>::deserialize(deserializer)?;
Self::from_str(s).map_err(D::Error::custom)
@ -362,7 +402,8 @@ pub struct InvalidRegion {
pub text: String,
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[serde(rename_all = "kebab-case")]
pub enum Region {
Europe,