You've already forked AstralRinth
forked from didirus/AstralRinth
Game Version types, indexing, and bugfixes (#91)
* Add types to game_versions, allow filtering by version type - Fixes an issue with version numbers in the initial mod indexing queue - Modifies the /api/v1/categories/game_versions route to take an optional query parameter `type` to filter the listed game versions - Creating tags is now idempotent - Creating game_versions now requires a JSON body that specifies the version type * Implement automatic indexing of new Minecraft versions It's currently set to run every 6 hours and isn't configurable; we could add config for it, but it doesn't seem likely to be rate limited or have issues with frequency.
This commit is contained in:
3
migrations/20201021214908_extend-game-version.sql
Normal file
3
migrations/20201021214908_extend-game-version.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
ALTER TABLE game_versions
|
||||||
|
ADD COLUMN type varchar(16) NOT NULL DEFAULT 'other';
|
||||||
141
sqlx-data.json
141
sqlx-data.json
@@ -278,26 +278,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"1b74bdb59773ffd2a78a56e4d920bb83c322e180e6174c741d4bb722c353de43": {
|
|
||||||
"query": "\n INSERT INTO loaders (loader)\n VALUES ($1)\n RETURNING id\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": "Int4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"1c7b0eb4341af5a7942e52f632cf582561f10b4b6a41a082fb8a60f04ac17c6e": {
|
"1c7b0eb4341af5a7942e52f632cf582561f10b4b6a41a082fb8a60f04ac17c6e": {
|
||||||
"query": "SELECT EXISTS(SELECT 1 FROM states WHERE id=$1)",
|
"query": "SELECT EXISTS(SELECT 1 FROM states WHERE id=$1)",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -499,6 +479,27 @@
|
|||||||
"nullable": []
|
"nullable": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"3d18702f07161c0cdbc31d70b89ffeb3678617ccc44dfc6fb03dd63f47226c7b": {
|
||||||
|
"query": "\n INSERT INTO game_versions (version, type)\n VALUES ($1, $2)\n ON CONFLICT (version) DO UPDATE\n SET type = excluded.type\n RETURNING id\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Varchar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"42e072309779598d0c213280dd8052d1b4889cb24ef5204ca13b74f693b94328": {
|
"42e072309779598d0c213280dd8052d1b4889cb24ef5204ca13b74f693b94328": {
|
||||||
"query": "\n SELECT user_id FROM team_members tm\n INNER JOIN mods ON mods.team_id = tm.team_id\n WHERE mods.id = $1\n ",
|
"query": "\n SELECT user_id FROM team_members tm\n INNER JOIN mods ON mods.team_id = tm.team_id\n WHERE mods.id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -554,6 +555,26 @@
|
|||||||
"nullable": []
|
"nullable": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"49e36828e3a0214b48234435e34311735ae32e08d8be1270f8f0db4b27e708ba": {
|
||||||
|
"query": "\n INSERT INTO loaders (loader)\n VALUES ($1)\n ON CONFLICT (loader) DO NOTHING\n RETURNING id\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"4c99c0840159d18e88cd6094a41117258f2337346c145d926b5b610c76b5125f": {
|
"4c99c0840159d18e88cd6094a41117258f2337346c145d926b5b610c76b5125f": {
|
||||||
"query": "\n SELECT c.category\n FROM mods_categories mc\n INNER JOIN categories c ON mc.joining_category_id=c.id\n WHERE mc.joining_mod_id = $1\n ",
|
"query": "\n SELECT c.category\n FROM mods_categories mc\n INNER JOIN categories c ON mc.joining_category_id=c.id\n WHERE mc.joining_mod_id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -620,26 +641,6 @@
|
|||||||
"nullable": []
|
"nullable": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"56cb9274e92f185dee3accf69cca2e34c035efbef908baefeb60548fb14e02bd": {
|
|
||||||
"query": "\n INSERT INTO categories (category)\n VALUES ($1)\n RETURNING id\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": "Int4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"59cf9d085593887595ea45246291f2cd64fc6677d551e96bdb60c09ff1eebf99": {
|
"59cf9d085593887595ea45246291f2cd64fc6677d551e96bdb60c09ff1eebf99": {
|
||||||
"query": "\n SELECT files.id, files.url, files.filename FROM files\n WHERE files.version_id = $1\n ",
|
"query": "\n SELECT files.id, files.url, files.filename FROM files\n WHERE files.version_id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -1224,26 +1225,6 @@
|
|||||||
"nullable": []
|
"nullable": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bee1e8b7f3588c6b0534443775f3d0d66d960e96a5ae8422aa96a69238f375a4": {
|
|
||||||
"query": "\n INSERT INTO game_versions (version)\n VALUES ($1)\n RETURNING id\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": "Int4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Varchar"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bf7f721664f5e0ed41adc41b5483037256635f28ff6c4e5d3cbcec4387f9c8ef": {
|
"bf7f721664f5e0ed41adc41b5483037256635f28ff6c4e5d3cbcec4387f9c8ef": {
|
||||||
"query": "SELECT EXISTS(SELECT 1 FROM users WHERE id=$1)",
|
"query": "SELECT EXISTS(SELECT 1 FROM users WHERE id=$1)",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -1735,6 +1716,26 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ec4a3ef12a35bb78002fdafccbdb198b15f9a0fdb2b3e4108f9081b7e56e8769": {
|
||||||
|
"query": "\n SELECT version FROM game_versions\n WHERE type = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "version",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"f0db9d8606ccc2196a9cfafe0e7090dab42bf790f25e0469b8947fac1cf043d5": {
|
"f0db9d8606ccc2196a9cfafe0e7090dab42bf790f25e0469b8947fac1cf043d5": {
|
||||||
"query": "\n SELECT version FROM game_versions\n WHERE id = $1\n ",
|
"query": "\n SELECT version FROM game_versions\n WHERE id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -1775,6 +1776,26 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"f12ae54acf02e06e9b8774e8c2ea95058a78f6d724645adcd02f9dea6538024f": {
|
||||||
|
"query": "\n INSERT INTO categories (category)\n VALUES ($1)\n ON CONFLICT (category) DO NOTHING\n RETURNING id\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"f78dac3d15be1ea0d0ed43a4beadc04ec00d8ba68be2bb68cbc3f2ebe5c93dbd": {
|
"f78dac3d15be1ea0d0ed43a4beadc04ec00d8ba68be2bb68cbc3f2ebe5c93dbd": {
|
||||||
"query": "\n SELECT title, description, downloads,\n icon_url, body_url, published,\n updated, status,\n issues_url, source_url, wiki_url,\n team_id\n FROM mods\n WHERE id = $1\n ",
|
"query": "\n SELECT title, description, downloads,\n icon_url, body_url, published,\n updated, status,\n issues_url, source_url, wiki_url,\n team_id\n FROM mods\n WHERE id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ impl<'a> CategoryBuilder<'a> {
|
|||||||
"
|
"
|
||||||
INSERT INTO categories (category)
|
INSERT INTO categories (category)
|
||||||
VALUES ($1)
|
VALUES ($1)
|
||||||
|
ON CONFLICT (category) DO NOTHING
|
||||||
RETURNING id
|
RETURNING id
|
||||||
",
|
",
|
||||||
self.name
|
self.name
|
||||||
@@ -255,6 +256,7 @@ impl<'a> LoaderBuilder<'a> {
|
|||||||
"
|
"
|
||||||
INSERT INTO loaders (loader)
|
INSERT INTO loaders (loader)
|
||||||
VALUES ($1)
|
VALUES ($1)
|
||||||
|
ON CONFLICT (loader) DO NOTHING
|
||||||
RETURNING id
|
RETURNING id
|
||||||
",
|
",
|
||||||
self.name
|
self.name
|
||||||
@@ -266,13 +268,15 @@ impl<'a> LoaderBuilder<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct GameVersionBuilder<'a> {
|
pub struct GameVersionBuilder<'a> {
|
||||||
pub version: Option<&'a str>,
|
pub version: Option<&'a str>,
|
||||||
|
pub version_type: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameVersion {
|
impl GameVersion {
|
||||||
pub fn builder() -> GameVersionBuilder<'static> {
|
pub fn builder() -> GameVersionBuilder<'static> {
|
||||||
GameVersionBuilder { version: None }
|
GameVersionBuilder::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_id<'a, E>(
|
pub async fn get_id<'a, E>(
|
||||||
@@ -302,7 +306,7 @@ impl GameVersion {
|
|||||||
Ok(result.map(|r| GameVersionId(r.id)))
|
Ok(result.map(|r| GameVersionId(r.id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_name<'a, E>(id: VersionId, exec: E) -> Result<String, DatabaseError>
|
pub async fn get_name<'a, E>(id: GameVersionId, exec: E) -> Result<String, DatabaseError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
@@ -311,7 +315,7 @@ impl GameVersion {
|
|||||||
SELECT version FROM game_versions
|
SELECT version FROM game_versions
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
",
|
",
|
||||||
id as VersionId
|
id as GameVersionId
|
||||||
)
|
)
|
||||||
.fetch_one(exec)
|
.fetch_one(exec)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -336,6 +340,25 @@ impl GameVersion {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn list_type<'a, E>(version_type: &str, exec: E) -> Result<Vec<String>, DatabaseError>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
|
{
|
||||||
|
let result = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT version FROM game_versions
|
||||||
|
WHERE type = $1
|
||||||
|
",
|
||||||
|
version_type
|
||||||
|
)
|
||||||
|
.fetch_many(exec)
|
||||||
|
.try_filter_map(|e| async { Ok(e.right().map(|c| c.version)) })
|
||||||
|
.try_collect::<Vec<String>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn remove<'a, E>(name: &str, exec: E) -> Result<Option<()>, DatabaseError>
|
pub async fn remove<'a, E>(name: &str, exec: E) -> Result<Option<()>, DatabaseError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
@@ -370,23 +393,44 @@ impl<'a> GameVersionBuilder<'a> {
|
|||||||
{
|
{
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
version: Some(version),
|
version: Some(version),
|
||||||
|
..self
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(DatabaseError::InvalidIdentifier(version.to_string()))
|
Err(DatabaseError::InvalidIdentifier(version.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn version_type(
|
||||||
|
self,
|
||||||
|
version_type: &'a str,
|
||||||
|
) -> Result<GameVersionBuilder<'a>, DatabaseError> {
|
||||||
|
if version_type
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
||||||
|
{
|
||||||
|
Ok(Self {
|
||||||
|
version_type: Some(version_type),
|
||||||
|
..self
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(DatabaseError::InvalidIdentifier(version_type.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert<'b, E>(self, exec: E) -> Result<GameVersionId, DatabaseError>
|
pub async fn insert<'b, E>(self, exec: E) -> Result<GameVersionId, DatabaseError>
|
||||||
where
|
where
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT INTO game_versions (version)
|
INSERT INTO game_versions (version, type)
|
||||||
VALUES ($1)
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT (version) DO UPDATE
|
||||||
|
SET type = excluded.type
|
||||||
RETURNING id
|
RETURNING id
|
||||||
",
|
",
|
||||||
self.version
|
self.version,
|
||||||
|
self.version_type,
|
||||||
)
|
)
|
||||||
.fetch_one(exec)
|
.fetch_one(exec)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -201,6 +201,8 @@ async fn main() -> std::io::Result<()> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheduler::schedule_versions(&mut scheduler, pool.clone(), skip_initial);
|
||||||
|
|
||||||
let allowed_origins = dotenv::var("CORS_ORIGINS")
|
let allowed_origins = dotenv::var("CORS_ORIGINS")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| serde_json::from_str::<Vec<String>>(&s).ok())
|
.and_then(|s| serde_json::from_str::<Vec<String>>(&s).ok())
|
||||||
|
|||||||
@@ -460,10 +460,10 @@ async fn mod_create_inner(
|
|||||||
status: status_id,
|
status: status_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
let versions_list = mod_builder
|
let versions_list = mod_create_data
|
||||||
.initial_versions
|
.initial_versions
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|v| v.game_versions.iter().map(|id| id.0.to_string()))
|
.flat_map(|v| v.game_versions.iter().map(|name| name.0.clone()))
|
||||||
.collect::<std::collections::HashSet<String>>()
|
.collect::<std::collections::HashSet<String>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ pub async fn category_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiE
|
|||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
// At some point this may take more info, but it should be able to
|
|
||||||
// remain idempotent
|
|
||||||
// TODO: don't fail if category already exists
|
|
||||||
#[put("category/{name}")]
|
#[put("category/{name}")]
|
||||||
pub async fn category_create(
|
pub async fn category_create(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@@ -93,9 +90,6 @@ pub async fn loader_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiErr
|
|||||||
Ok(HttpResponse::Ok().json(results))
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
// At some point this may take more info, but it should be able to
|
|
||||||
// remain idempotent
|
|
||||||
// TODO: don't fail if loader already exists
|
|
||||||
#[put("loader/{name}")]
|
#[put("loader/{name}")]
|
||||||
pub async fn loader_create(
|
pub async fn loader_create(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
@@ -152,19 +146,38 @@ pub async fn loader_delete(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("game_version")]
|
#[derive(serde::Deserialize)]
|
||||||
pub async fn game_version_list(pool: web::Data<PgPool>) -> Result<HttpResponse, ApiError> {
|
pub struct GameVersionQueryData {
|
||||||
let results = GameVersion::list(&**pool).await?;
|
#[serde(rename = "type")]
|
||||||
Ok(HttpResponse::Ok().json(results))
|
type_: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("game_version")]
|
||||||
|
pub async fn game_version_list(
|
||||||
|
pool: web::Data<PgPool>,
|
||||||
|
query: web::Query<GameVersionQueryData>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
if let Some(type_) = &query.type_ {
|
||||||
|
let results = GameVersion::list_type(type_, &**pool).await?;
|
||||||
|
Ok(HttpResponse::Ok().json(results))
|
||||||
|
} else {
|
||||||
|
let results = GameVersion::list(&**pool).await?;
|
||||||
|
Ok(HttpResponse::Ok().json(results))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct GameVersionData {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
type_: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// At some point this may take more info, but it should be able to
|
|
||||||
// remain idempotent
|
|
||||||
#[put("game_version/{name}")]
|
#[put("game_version/{name}")]
|
||||||
pub async fn game_version_create(
|
pub async fn game_version_create(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
game_version: web::Path<(String,)>,
|
game_version: web::Path<(String,)>,
|
||||||
|
version_data: web::Json<GameVersionData>,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
check_is_admin_from_headers(
|
check_is_admin_from_headers(
|
||||||
req.headers(),
|
req.headers(),
|
||||||
@@ -178,8 +191,12 @@ pub async fn game_version_create(
|
|||||||
|
|
||||||
let name = game_version.into_inner().0;
|
let name = game_version.into_inner().0;
|
||||||
|
|
||||||
|
// The version type currently isn't limited, but it should be one of:
|
||||||
|
// "release", "snapshot", "alpha", "beta", "other"
|
||||||
|
|
||||||
let _id = GameVersion::builder()
|
let _id = GameVersion::builder()
|
||||||
.version(&name)?
|
.version(&name)?
|
||||||
|
.version_type(&version_data.type_)?
|
||||||
.insert(&**pool)
|
.insert(&**pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|||||||
103
src/scheduler.rs
103
src/scheduler.rs
@@ -28,3 +28,106 @@ impl Drop for Scheduler {
|
|||||||
self.arbiter.stop();
|
self.arbiter.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use log::{info, warn};
|
||||||
|
|
||||||
|
pub fn schedule_versions(
|
||||||
|
scheduler: &mut Scheduler,
|
||||||
|
pool: sqlx::Pool<sqlx::Postgres>,
|
||||||
|
skip_initial: bool,
|
||||||
|
) {
|
||||||
|
// Check mojang's versions every 6 hours
|
||||||
|
let version_index_interval = std::time::Duration::from_secs(60 * 60 * 6);
|
||||||
|
|
||||||
|
let mut skip = skip_initial;
|
||||||
|
scheduler.run(version_index_interval, move || {
|
||||||
|
let pool_ref = pool.clone();
|
||||||
|
let local_skip = skip;
|
||||||
|
if skip {
|
||||||
|
skip = false;
|
||||||
|
}
|
||||||
|
async move {
|
||||||
|
if local_skip {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
info!("Indexing game versions list from Mojang");
|
||||||
|
let result = update_versions(&pool_ref).await;
|
||||||
|
if let Err(e) = result {
|
||||||
|
warn!("Version update failed: {}", e);
|
||||||
|
}
|
||||||
|
info!("Done indexing game versions");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum VersionIndexingError {
|
||||||
|
#[error("Network error while updating game versions list: {0}")]
|
||||||
|
NetworkError(#[from] reqwest::Error),
|
||||||
|
#[error("Database error while updating game versions list: {0}")]
|
||||||
|
DatabaseError(#[from] crate::database::models::DatabaseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct InputFormat<'a> {
|
||||||
|
// latest: LatestFormat,
|
||||||
|
versions: Vec<VersionFormat<'a>>,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct VersionFormat<'a> {
|
||||||
|
id: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
type_: std::borrow::Cow<'a, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_versions(pool: &sqlx::Pool<sqlx::Postgres>) -> Result<(), VersionIndexingError> {
|
||||||
|
let input = reqwest::get("https://launchermeta.mojang.com/mc/game/version_manifest.json")
|
||||||
|
.await?
|
||||||
|
.json::<InputFormat>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut skipped_versions_count = 0u32;
|
||||||
|
|
||||||
|
for version in input.versions.into_iter() {
|
||||||
|
let name = version.id;
|
||||||
|
if !name
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_alphanumeric() || "-_.".contains(c))
|
||||||
|
{
|
||||||
|
// We'll deal with these manually
|
||||||
|
skipped_versions_count += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let type_ = match &*version.type_ {
|
||||||
|
"release" => "release",
|
||||||
|
"snapshot" => "snapshot",
|
||||||
|
"old_alpha" => "alpha",
|
||||||
|
"old_beta" => "beta",
|
||||||
|
_ => "other",
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::database::models::categories::GameVersion::builder()
|
||||||
|
.version(&name)?
|
||||||
|
.version_type(type_)?
|
||||||
|
.insert(pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if skipped_versions_count > 0 {
|
||||||
|
// This will currently always trigger due to 1.14 pre releases
|
||||||
|
// and the shareware april fools update. We could set a threshold
|
||||||
|
// that accounts for those versions and update it whenever we
|
||||||
|
// manually fix another version.
|
||||||
|
warn!(
|
||||||
|
"Skipped {} game versions; check for new versions and add them manually",
|
||||||
|
skipped_versions_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user