Compare commits
5 Commits
mapfix-che
...
feature/ma
| Author | SHA1 | Date | |
|---|---|---|---|
| f4209ecd0a | |||
| e83c9db866 | |||
| 7bec80a2fe | |||
| 3e77edb1cc | |||
| 0b7ca534f3 |
37
.drone.yml
37
.drone.yml
@@ -32,6 +32,15 @@ steps:
|
||||
- master
|
||||
- staging
|
||||
|
||||
- name: build-combobulator
|
||||
image: clux/muslrust:1.91.0-stable
|
||||
commands:
|
||||
- make build-combobulator
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
|
||||
- name: build-frontend
|
||||
image: oven/bun:1.3.3
|
||||
commands:
|
||||
@@ -112,6 +121,29 @@ steps:
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: image-combobulator
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: registry.itzana.me
|
||||
repo: registry.itzana.me/strafesnet/maptest-combobulator
|
||||
tags:
|
||||
- ${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
|
||||
- ${DRONE_BRANCH}
|
||||
username:
|
||||
from_secret: REGISTRY_USER
|
||||
password:
|
||||
from_secret: REGISTRY_PASS
|
||||
dockerfile: combobulator/Containerfile
|
||||
context: .
|
||||
depends_on:
|
||||
- build-combobulator
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- staging
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: deploy
|
||||
image: argoproj/argocd:latest
|
||||
commands:
|
||||
@@ -119,6 +151,7 @@ steps:
|
||||
- argocd app --grpc-web set ${DRONE_BRANCH}-maps-service --kustomize-image registry.itzana.me/strafesnet/maptest-api:${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
|
||||
- argocd app --grpc-web set ${DRONE_BRANCH}-maps-service --kustomize-image registry.itzana.me/strafesnet/maptest-frontend:${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
|
||||
- argocd app --grpc-web set ${DRONE_BRANCH}-maps-service --kustomize-image registry.itzana.me/strafesnet/maptest-validator:${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
|
||||
- argocd app --grpc-web set ${DRONE_BRANCH}-maps-service --kustomize-image registry.itzana.me/strafesnet/maptest-combobulator:${DRONE_BRANCH}-${DRONE_BUILD_NUMBER}
|
||||
environment:
|
||||
USERNAME:
|
||||
from_secret: ARGO_USER
|
||||
@@ -128,6 +161,7 @@ steps:
|
||||
- image-backend
|
||||
- image-frontend
|
||||
- image-validator
|
||||
- image-combobulator
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
@@ -143,12 +177,13 @@ steps:
|
||||
depends_on:
|
||||
- build-backend
|
||||
- build-validator
|
||||
- build-combobulator
|
||||
- build-frontend
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
---
|
||||
kind: signature
|
||||
hmac: 6de9d4b91f14b30561856daf275d1fd523e1ce7a5a3651b660f0d8907b4692fb
|
||||
hmac: 2d2a3b50b5864bd79efacf31f71b5a409a1782f6dbfb4669a418f577cc5517bd
|
||||
|
||||
...
|
||||
|
||||
2961
Cargo.lock
generated
2961
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"combobulator",
|
||||
"validation",
|
||||
"submissions-api-rs",
|
||||
]
|
||||
|
||||
15
Makefile
15
Makefile
@@ -9,12 +9,15 @@ build-backend:
|
||||
build-validator:
|
||||
cargo build --release --target x86_64-unknown-linux-musl --bin maps-validation
|
||||
|
||||
build-combobulator:
|
||||
cargo build --release --target x86_64-unknown-linux-musl --bin maps-combobulator
|
||||
|
||||
build-frontend:
|
||||
rm -rf web/build
|
||||
cd web && bun install --frozen-lockfile
|
||||
cd web && bun run build
|
||||
|
||||
build: build-backend build-validator build-frontend
|
||||
build: build-backend build-validator build-combobulator build-frontend
|
||||
|
||||
# image
|
||||
image-backend:
|
||||
@@ -23,6 +26,9 @@ image-backend:
|
||||
image-validator:
|
||||
docker build . -f validation/Containerfile -t maptest-validator
|
||||
|
||||
image-combobulator:
|
||||
docker build . -f combobulator/Containerfile -t maptest-combobulator
|
||||
|
||||
image-frontend:
|
||||
docker build web -f web/Containerfile -t maptest-frontend
|
||||
|
||||
@@ -33,9 +39,12 @@ docker-backend:
|
||||
docker-validator:
|
||||
make build-validator
|
||||
make image-validator
|
||||
docker-combobulator:
|
||||
make build-combobulator
|
||||
make image-combobulator
|
||||
docker-frontend:
|
||||
make image-frontend
|
||||
|
||||
docker: docker-backend docker-validator docker-frontend
|
||||
docker: docker-backend docker-validator docker-combobulator docker-frontend
|
||||
|
||||
.PHONY: clean build-backend build-validator build-frontend build image-backend image-validator image-frontend docker-backend docker-validator docker-frontend docker
|
||||
.PHONY: clean build-backend build-validator build-combobulator build-frontend build image-backend image-validator image-combobulator image-frontend docker-backend docker-validator docker-combobulator docker-frontend docker
|
||||
|
||||
15
combobulator/Cargo.toml
Normal file
15
combobulator/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "maps-combobulator"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
async-nats = "0.45.0"
|
||||
aws-config = { version = "1", features = ["behavior-version-latest"] }
|
||||
aws-sdk-s3 = "1"
|
||||
map-tool = { version = "2.0.0", registry = "strafesnet" }
|
||||
rbx_asset = { version = "0.5.0", features = ["gzip", "rustls-tls"], default-features = false, registry = "strafesnet" }
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread", "signal"] }
|
||||
tokio-stream = "0.1"
|
||||
3
combobulator/Containerfile
Normal file
3
combobulator/Containerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM alpine:3.21 AS runtime
|
||||
COPY /target/x86_64-unknown-linux-musl/release/maps-combobulator /
|
||||
ENTRYPOINT ["/maps-combobulator"]
|
||||
169
combobulator/src/main.rs
Normal file
169
combobulator/src/main.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
mod nats_types;
|
||||
mod process;
|
||||
mod s3;
|
||||
|
||||
const SUBJECT_MAPFIX_RELEASE:&str="maptest.mapfixes.release";
|
||||
const SUBJECT_SUBMISSION_BATCHRELEASE:&str="maptest.submissions.batchrelease";
|
||||
const SUBJECT_SUBMISSION_RELEASE:&str="maptest.combobulator.submissions.release";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StartupError{
|
||||
NatsConnect(async_nats::ConnectError),
|
||||
NatsGetStream(async_nats::jetstream::context::GetStreamError),
|
||||
NatsConsumer(async_nats::jetstream::stream::ConsumerError),
|
||||
NatsConsumerUpdate(async_nats::jetstream::stream::ConsumerUpdateError),
|
||||
NatsStream(async_nats::jetstream::consumer::StreamError),
|
||||
}
|
||||
impl std::fmt::Display for StartupError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for StartupError{}
|
||||
|
||||
#[expect(dead_code)]
|
||||
#[derive(Debug)]
|
||||
enum HandleMessageError{
|
||||
Json(serde_json::Error),
|
||||
UnknownSubject(String),
|
||||
Process(process::Error),
|
||||
Ack(async_nats::Error),
|
||||
Publish(async_nats::jetstream::context::PublishError),
|
||||
}
|
||||
impl std::fmt::Display for HandleMessageError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for HandleMessageError{}
|
||||
|
||||
fn from_slice<'a,T:serde::de::Deserialize<'a>>(slice:&'a [u8])->Result<T,HandleMessageError>{
|
||||
serde_json::from_slice(slice).map_err(HandleMessageError::Json)
|
||||
}
|
||||
|
||||
async fn handle_message(
|
||||
processor:&process::Processor,
|
||||
jetstream:&async_nats::jetstream::Context,
|
||||
message:async_nats::jetstream::Message,
|
||||
)->Result<(),HandleMessageError>{
|
||||
match message.subject.as_str(){
|
||||
SUBJECT_MAPFIX_RELEASE=>{
|
||||
let request:nats_types::ReleaseMapfixRequest=from_slice(&message.payload)?;
|
||||
processor.handle_mapfix_release(request).await.map_err(HandleMessageError::Process)?;
|
||||
message.ack().await.map_err(HandleMessageError::Ack)?;
|
||||
},
|
||||
SUBJECT_SUBMISSION_BATCHRELEASE=>{
|
||||
// split batch into individual messages and republish
|
||||
let batch:nats_types::ReleaseSubmissionsBatchRequest=from_slice(&message.payload)?;
|
||||
println!("[combobulator] Splitting batch release (operation {}, {} submissions)",
|
||||
batch.OperationID,batch.Submissions.len());
|
||||
for submission in batch.Submissions{
|
||||
let payload=serde_json::to_vec(&submission).map_err(HandleMessageError::Json)?;
|
||||
jetstream.publish(SUBJECT_SUBMISSION_RELEASE,payload.into())
|
||||
.await.map_err(HandleMessageError::Publish)?;
|
||||
println!("[combobulator] Published individual release for submission {}",submission.SubmissionID);
|
||||
}
|
||||
// ack the batch now that all individual messages are queued
|
||||
message.ack().await.map_err(HandleMessageError::Ack)?;
|
||||
},
|
||||
SUBJECT_SUBMISSION_RELEASE=>{
|
||||
let request:nats_types::ReleaseSubmissionRequest=from_slice(&message.payload)?;
|
||||
processor.handle_submission_release(request).await.map_err(HandleMessageError::Process)?;
|
||||
message.ack().await.map_err(HandleMessageError::Ack)?;
|
||||
},
|
||||
other=>return Err(HandleMessageError::UnknownSubject(other.to_owned())),
|
||||
}
|
||||
|
||||
println!("[combobulator] Message processed and acked");
|
||||
Ok(())
|
||||
}S
|
||||
|
||||
#[tokio::main]
|
||||
async fn main()->Result<(),StartupError>{
|
||||
// roblox cloud api for downloading models
|
||||
let api_key=std::env::var("RBX_API_KEY").expect("RBX_API_KEY env required");
|
||||
let cloud_context=rbx_asset::cloud::Context::new(rbx_asset::cloud::ApiKey::new(api_key));
|
||||
|
||||
// roblox cookie api for downloading assets (textures, meshes, unions)
|
||||
let cookie=std::env::var("RBXCOOKIE").expect("RBXCOOKIE env required");
|
||||
let cookie_context=rbx_asset::cookie::Context::new(rbx_asset::cookie::Cookie::new(cookie));
|
||||
|
||||
// s3
|
||||
let s3_bucket=std::env::var("S3_BUCKET").expect("S3_BUCKET env required");
|
||||
let s3_config=aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
|
||||
let s3_client=aws_sdk_s3::Client::new(&s3_config);
|
||||
let s3_cache=s3::S3Cache::new(s3_client,s3_bucket);
|
||||
|
||||
let processor=process::Processor{
|
||||
cloud_context,
|
||||
cookie_context,
|
||||
s3:s3_cache,
|
||||
};
|
||||
|
||||
// nats
|
||||
let nats_host=std::env::var("NATS_HOST").expect("NATS_HOST env required");
|
||||
|
||||
const STREAM_NAME:&str="maptest";
|
||||
const DURABLE_NAME:&str="combobulator";
|
||||
|
||||
let filter_subjects=vec![
|
||||
SUBJECT_MAPFIX_RELEASE.to_owned(),
|
||||
SUBJECT_SUBMISSION_BATCHRELEASE.to_owned(),
|
||||
SUBJECT_SUBMISSION_RELEASE.to_owned(),
|
||||
];
|
||||
|
||||
let nats_config=async_nats::jetstream::consumer::pull::Config{
|
||||
name:Some(DURABLE_NAME.to_owned()),
|
||||
durable_name:Some(DURABLE_NAME.to_owned()),
|
||||
filter_subjects:filter_subjects.clone(),
|
||||
ack_wait:std::time::Duration::from_secs(900), // 15 minutes for processing
|
||||
max_deliver:5, // retry up to 5 times
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let nasty=async_nats::connect(nats_host).await.map_err(StartupError::NatsConnect)?;
|
||||
let jetstream=async_nats::jetstream::new(nasty);
|
||||
let stream=jetstream.get_stream(STREAM_NAME).await.map_err(StartupError::NatsGetStream)?;
|
||||
let consumer=stream.get_or_create_consumer(DURABLE_NAME,nats_config.clone()).await.map_err(StartupError::NatsConsumer)?;
|
||||
|
||||
// update consumer config if filter subjects changed
|
||||
if consumer.cached_info().config.filter_subjects!=filter_subjects{
|
||||
stream.update_consumer(nats_config).await.map_err(StartupError::NatsConsumerUpdate)?;
|
||||
}
|
||||
|
||||
let mut messages=consumer.messages().await.map_err(StartupError::NatsStream)?;
|
||||
|
||||
// SIGTERM graceful shutdown
|
||||
let mut sig_term=tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
||||
.expect("Failed to create SIGTERM signal listener");
|
||||
|
||||
println!("[combobulator] Started, waiting for messages...");
|
||||
|
||||
// sequential processing loop - one message at a time
|
||||
let main_loop=async{
|
||||
while let Some(message_result)=messages.next().await{
|
||||
match message_result{
|
||||
Ok(message)=>{
|
||||
match handle_message(&processor,&jetstream,message).await{
|
||||
Ok(())=>println!("[combobulator] Success"),
|
||||
Err(e)=>println!("[combobulator] Error: {e}"),
|
||||
}
|
||||
},
|
||||
Err(e)=>println!("[combobulator] Message stream error: {e}"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokio::select!{
|
||||
_=sig_term.recv()=>{
|
||||
println!("[combobulator] Received SIGTERM, shutting down");
|
||||
},
|
||||
_=main_loop=>{
|
||||
println!("[combobulator] Message stream ended");
|
||||
},
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
29
combobulator/src/nats_types.rs
Normal file
29
combobulator/src/nats_types.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#[expect(nonstandard_style,dead_code)]
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct ReleaseMapfixRequest{
|
||||
pub MapfixID:u64,
|
||||
pub ModelID:u64,
|
||||
pub ModelVersion:u64,
|
||||
pub TargetAssetID:u64,
|
||||
}
|
||||
|
||||
#[expect(nonstandard_style)]
|
||||
#[derive(serde::Deserialize,serde::Serialize)]
|
||||
pub struct ReleaseSubmissionRequest{
|
||||
pub SubmissionID:u64,
|
||||
pub ReleaseDate:i64,
|
||||
pub ModelID:u64,
|
||||
pub ModelVersion:u64,
|
||||
pub UploadedAssetID:u64,
|
||||
pub DisplayName:String,
|
||||
pub Creator:String,
|
||||
pub GameID:u32,
|
||||
pub Submitter:u64,
|
||||
}
|
||||
|
||||
#[expect(nonstandard_style)]
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct ReleaseSubmissionsBatchRequest{
|
||||
pub Submissions:Vec<ReleaseSubmissionRequest>,
|
||||
pub OperationID:u32,
|
||||
}
|
||||
144
combobulator/src/process.rs
Normal file
144
combobulator/src/process.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use crate::nats_types::ReleaseMapfixRequest;
|
||||
use crate::s3::S3Cache;
|
||||
|
||||
#[expect(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Error{
|
||||
Download(rbx_asset::cloud::GetError),
|
||||
NonFreeModel,
|
||||
GetAssets(map_tool::roblox::UniqueAssetError),
|
||||
DownloadAsset(map_tool::roblox::DownloadAssetError),
|
||||
ConvertTexture(map_tool::roblox::ConvertTextureError),
|
||||
ConvertSnf(map_tool::roblox::ConvertError),
|
||||
S3Get(crate::s3::GetError),
|
||||
S3Put(crate::s3::PutError),
|
||||
}
|
||||
impl std::fmt::Display for Error{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error{}
|
||||
|
||||
pub struct Processor{
|
||||
pub cloud_context:rbx_asset::cloud::Context,
|
||||
pub cookie_context:rbx_asset::cookie::Context,
|
||||
pub s3:S3Cache,
|
||||
}
|
||||
|
||||
impl Processor{
|
||||
/// Download a model version from Roblox cloud API.
|
||||
async fn download_model(&self,model_id:u64,model_version:u64)->Result<Vec<u8>,Error>{
|
||||
let location=self.cloud_context.get_asset_version_location(
|
||||
rbx_asset::cloud::GetAssetVersionRequest{
|
||||
asset_id:model_id,
|
||||
version:model_version,
|
||||
}
|
||||
).await.map_err(Error::Download)?;
|
||||
|
||||
let location=location.location.ok_or(Error::NonFreeModel)?;
|
||||
|
||||
let maybe_gzip=self.cloud_context.get_asset(&location).await.map_err(Error::Download)?;
|
||||
|
||||
Ok(maybe_gzip.into_inner().to_vec())
|
||||
}
|
||||
|
||||
/// Process a single model: extract assets, cache to S3, build SNF.
|
||||
async fn process_model(&self,model_id:u64,model_version:u64)->Result<(),Error>{
|
||||
println!("[combobulator] Downloading model {model_id} v{model_version}");
|
||||
let rbxl_bytes=self.download_model(model_id,model_version).await?;
|
||||
|
||||
// extract unique assets from the file
|
||||
let assets=map_tool::roblox::get_unique_assets_from_file(&rbxl_bytes)
|
||||
.map_err(Error::GetAssets)?;
|
||||
|
||||
// process textures: download, cache, convert to DDS
|
||||
for id in &assets.textures{
|
||||
let asset_id=id.0;
|
||||
let dds_key=S3Cache::texture_dds_key(asset_id);
|
||||
|
||||
// skip if DDS already cached
|
||||
if self.s3.get(&dds_key).await.map_err(Error::S3Get)?.is_some(){
|
||||
println!("[combobulator] Texture {asset_id} already cached, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
// check raw cache, download if missing
|
||||
let raw_key=S3Cache::texture_raw_key(asset_id);
|
||||
let raw_data=match self.s3.get(&raw_key).await.map_err(Error::S3Get)?{
|
||||
Some(cached)=>cached,
|
||||
None=>{
|
||||
println!("[combobulator] Downloading texture {asset_id}");
|
||||
let data=map_tool::roblox::download_asset(&self.cookie_context,asset_id)
|
||||
.await.map_err(Error::DownloadAsset)?;
|
||||
self.s3.put(&raw_key,data.clone()).await.map_err(Error::S3Put)?;
|
||||
data
|
||||
},
|
||||
};
|
||||
|
||||
// convert to DDS and upload
|
||||
let dds=map_tool::roblox::convert_texture_to_dds(&raw_data)
|
||||
.map_err(Error::ConvertTexture)?;
|
||||
self.s3.put(&dds_key,dds).await.map_err(Error::S3Put)?;
|
||||
println!("[combobulator] Texture {asset_id} processed");
|
||||
}
|
||||
|
||||
// process meshes
|
||||
for id in &assets.meshes{
|
||||
let asset_id=id.0;
|
||||
let mesh_key=S3Cache::mesh_key(asset_id);
|
||||
|
||||
if self.s3.get(&mesh_key).await.map_err(Error::S3Get)?.is_some(){
|
||||
println!("[combobulator] Mesh {asset_id} already cached, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("[combobulator] Downloading mesh {asset_id}");
|
||||
let data=map_tool::roblox::download_asset(&self.cookie_context,asset_id)
|
||||
.await.map_err(Error::DownloadAsset)?;
|
||||
self.s3.put(&mesh_key,data).await.map_err(Error::S3Put)?;
|
||||
println!("[combobulator] Mesh {asset_id} processed");
|
||||
}
|
||||
|
||||
// process unions
|
||||
for id in &assets.unions{
|
||||
let asset_id=id.0;
|
||||
let union_key=S3Cache::union_key(asset_id);
|
||||
|
||||
if self.s3.get(&union_key).await.map_err(Error::S3Get)?.is_some(){
|
||||
println!("[combobulator] Union {asset_id} already cached, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("[combobulator] Downloading union {asset_id}");
|
||||
let data=map_tool::roblox::download_asset(&self.cookie_context,asset_id)
|
||||
.await.map_err(Error::DownloadAsset)?;
|
||||
self.s3.put(&union_key,data).await.map_err(Error::S3Put)?;
|
||||
println!("[combobulator] Union {asset_id} processed");
|
||||
}
|
||||
|
||||
// convert to SNF and upload
|
||||
println!("[combobulator] Converting to SNF");
|
||||
let output=map_tool::roblox::convert_to_snf(&rbxl_bytes)
|
||||
.map_err(Error::ConvertSnf)?;
|
||||
let snf_key=S3Cache::snf_key(model_id,model_version);
|
||||
self.s3.put(&snf_key,output.snf).await.map_err(Error::S3Put)?;
|
||||
println!("[combobulator] SNF uploaded to {snf_key}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle a mapfix release message.
|
||||
pub async fn handle_mapfix_release(&self,request:ReleaseMapfixRequest)->Result<(),Error>{
|
||||
println!("[combobulator] Processing mapfix {} (model {} v{})",
|
||||
request.MapfixID,request.ModelID,request.ModelVersion);
|
||||
self.process_model(request.ModelID,request.ModelVersion).await
|
||||
}
|
||||
|
||||
/// Handle an individual submission release message.
|
||||
pub async fn handle_submission_release(&self,request:crate::nats_types::ReleaseSubmissionRequest)->Result<(),Error>{
|
||||
println!("[combobulator] Processing submission {} (model {} v{})",
|
||||
request.SubmissionID,request.ModelID,request.ModelVersion);
|
||||
self.process_model(request.ModelID,request.ModelVersion).await
|
||||
}
|
||||
}
|
||||
96
combobulator/src/s3.rs
Normal file
96
combobulator/src/s3.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use aws_sdk_s3::Client;
|
||||
use aws_sdk_s3::primitives::ByteStream;
|
||||
|
||||
#[expect(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum GetError{
|
||||
Get(aws_sdk_s3::error::SdkError<aws_sdk_s3::operation::get_object::GetObjectError>),
|
||||
Collect(aws_sdk_s3::primitives::ByteStreamError),
|
||||
}
|
||||
impl std::fmt::Display for GetError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for GetError{}
|
||||
|
||||
#[expect(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum PutError{
|
||||
Put(aws_sdk_s3::error::SdkError<aws_sdk_s3::operation::put_object::PutObjectError>),
|
||||
}
|
||||
impl std::fmt::Display for PutError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for PutError{}
|
||||
|
||||
pub struct S3Cache{
|
||||
client:Client,
|
||||
bucket:String,
|
||||
}
|
||||
|
||||
impl S3Cache{
|
||||
pub fn new(client:Client,bucket:String)->Self{
|
||||
Self{client,bucket}
|
||||
}
|
||||
|
||||
/// Try to get a cached object. Returns None if the key doesn't exist.
|
||||
pub async fn get(&self,key:&str)->Result<Option<Vec<u8>>,GetError>{
|
||||
match self.client.get_object()
|
||||
.bucket(&self.bucket)
|
||||
.key(key)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(output)=>{
|
||||
let bytes=output.body.collect().await.map_err(GetError::Collect)?;
|
||||
Ok(Some(bytes.to_vec()))
|
||||
},
|
||||
Err(e)=>{
|
||||
// check if it's a NoSuchKey error
|
||||
if let aws_sdk_s3::error::SdkError::ServiceError(ref service_err)=e{
|
||||
if service_err.err().is_no_such_key(){
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Err(GetError::Get(e))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Put an object into S3.
|
||||
pub async fn put(&self,key:&str,data:Vec<u8>)->Result<(),PutError>{
|
||||
self.client.put_object()
|
||||
.bucket(&self.bucket)
|
||||
.key(key)
|
||||
.body(ByteStream::from(data))
|
||||
.send()
|
||||
.await
|
||||
.map_err(PutError::Put)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// S3 key helpers
|
||||
|
||||
pub fn texture_raw_key(asset_id:u64)->String{
|
||||
format!("assets/textures/{asset_id}.raw")
|
||||
}
|
||||
|
||||
pub fn texture_dds_key(asset_id:u64)->String{
|
||||
format!("assets/textures/{asset_id}.dds")
|
||||
}
|
||||
|
||||
pub fn mesh_key(asset_id:u64)->String{
|
||||
format!("assets/meshes/{asset_id}")
|
||||
}
|
||||
|
||||
pub fn union_key(asset_id:u64)->String{
|
||||
format!("assets/unions/{asset_id}")
|
||||
}
|
||||
|
||||
pub fn snf_key(model_id:u64,model_version:u64)->String{
|
||||
format!("maps/{model_id}/v{model_version}/map.snfm")
|
||||
}
|
||||
}
|
||||
2
go.mod
2
go.mod
@@ -6,7 +6,7 @@ toolchain go1.24.5
|
||||
|
||||
require (
|
||||
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20250815013325-1c84f73bdcb1
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20251228204118-c20dbb42afec
|
||||
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9
|
||||
github.com/dchest/siphash v1.2.3
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
|
||||
38
go.sum
38
go.sum
@@ -2,8 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed h1:eGWIQx2AOrSsLC2dieuSs8MCliRE60tvpZnmxsTBtKc=
|
||||
git.itzana.me/StrafesNET/dev-service v0.0.0-20250628052121-92af8193b5ed/go.mod h1:KJal0K++M6HEzSry6JJ2iDPZtOQn5zSstNlDbU3X4Jg=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20250815013325-1c84f73bdcb1 h1:imXibfeYcae6og0TTDUFRQ3CQtstGjIoLbCn+pezD2o=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20250815013325-1c84f73bdcb1/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20251228204118-c20dbb42afec h1:JSar9If1kzb02+Erp+zmSqHKWPPP2NqMQVK15pRmkLE=
|
||||
git.itzana.me/strafesnet/go-grpc v0.0.0-20251228204118-c20dbb42afec/go.mod h1:X7XTRUScRkBWq8q8bplbeso105RPDlnY7J6Wy1IwBMs=
|
||||
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9 h1:7lU6jyR7S7Rhh1dnUp7GyIRHUTBXZagw8F4n4hOyxLw=
|
||||
git.itzana.me/strafesnet/utils v0.0.0-20220716194944-d8ca164052f9/go.mod h1:uyYerSieEt4v0MJCdPLppG0LtJ4Yj035vuTetWGsxjY=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -47,8 +47,6 @@ github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
|
||||
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -57,8 +55,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
@@ -73,8 +69,6 @@ github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||
github.com/go-faster/jx v1.1.0 h1:ZsW3wD+snOdmTDy9eIVgQdjUpXRRV4rqW8NS3t+20bg=
|
||||
github.com/go-faster/jx v1.1.0/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg=
|
||||
github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI=
|
||||
github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE=
|
||||
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
|
||||
@@ -152,8 +146,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
@@ -173,11 +165,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -192,8 +181,6 @@ github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDm
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/ogen-go/ogen v1.2.1 h1:C5A0lvUMu2wl+eWIxnpXMWnuOJ26a2FyzR1CIC2qG0M=
|
||||
github.com/ogen-go/ogen v1.2.1/go.mod h1:P2zQdEu8UqaVRfD5GEFvl+9q63VjMLvDquq1wVbyInM=
|
||||
github.com/ogen-go/ogen v1.18.0 h1:6RQ7lFBjOeNaUWu4getfqIh4GJbEY4hqKuzDtec/g60=
|
||||
github.com/ogen-go/ogen v1.18.0/go.mod h1:dHFr2Wf6cA7tSxMI+zPC21UR5hAlDw8ZYUkK3PziURY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
@@ -208,8 +195,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
@@ -261,8 +246,6 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
@@ -271,21 +254,15 @@ golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -301,8 +278,6 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -312,8 +287,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -329,11 +302,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -344,8 +314,6 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -355,8 +323,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"git.itzana.me/strafesnet/go-grpc/auth"
|
||||
"git.itzana.me/strafesnet/go-grpc/maps"
|
||||
"git.itzana.me/strafesnet/go-grpc/maps_extended"
|
||||
"git.itzana.me/strafesnet/go-grpc/mapfixes"
|
||||
"git.itzana.me/strafesnet/go-grpc/submissions"
|
||||
"git.itzana.me/strafesnet/go-grpc/users"
|
||||
"git.itzana.me/strafesnet/go-grpc/validator"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/api"
|
||||
@@ -204,7 +206,11 @@ func serve(ctx *cli.Context) error {
|
||||
grpcServer := grpc.NewServer()
|
||||
|
||||
maps_controller := controller.NewMapsController(&svc_inner)
|
||||
mapfixes_controller := controller.NewMapfixesController(&svc_inner)
|
||||
submissions_controller := controller.NewSubmissionsController(&svc_inner)
|
||||
maps_extended.RegisterMapsServiceServer(grpcServer,&maps_controller)
|
||||
mapfixes.RegisterMapfixesServiceServer(grpcServer,&mapfixes_controller)
|
||||
submissions.RegisterSubmissionsServiceServer(grpcServer,&submissions_controller)
|
||||
|
||||
mapfix_controller := validator_controller.NewMapfixesController(&svc_inner)
|
||||
operation_controller := validator_controller.NewOperationsController(&svc_inner)
|
||||
|
||||
149
pkg/controller/mapfixes.go
Normal file
149
pkg/controller/mapfixes.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/mapfixes"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
type Mapfixes struct {
|
||||
*mapfixes.UnimplementedMapfixesServiceServer
|
||||
inner *service.Service
|
||||
}
|
||||
|
||||
func NewMapfixesController(
|
||||
inner *service.Service,
|
||||
) Mapfixes {
|
||||
return Mapfixes{
|
||||
inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Mapfixes) Get(ctx context.Context, request *mapfixes.MapfixId) (*mapfixes.MapfixResponse, error) {
|
||||
item, err := svc.inner.GetMapfix(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var validated_asset_id *uint64
|
||||
if item.ValidatedAssetID != 0 {
|
||||
validated_asset_id = &item.ValidatedAssetID
|
||||
}
|
||||
var validated_asset_version *uint64
|
||||
if item.ValidatedAssetVersion != 0 {
|
||||
validated_asset_version = &item.ValidatedAssetVersion
|
||||
}
|
||||
return &mapfixes.MapfixResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
AssetID: item.AssetID,
|
||||
ValidatedAssetID: validated_asset_id,
|
||||
ValidatedAssetVersion: validated_asset_version,
|
||||
TargetAssetID: item.TargetAssetID,
|
||||
StatusID: mapfixes.MapfixStatus(item.StatusID),
|
||||
}, nil
|
||||
}
|
||||
func (svc *Mapfixes) GetList(ctx context.Context, request *mapfixes.MapfixIdList) (*mapfixes.MapfixList, error) {
|
||||
items, err := svc.inner.GetMapfixList(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := mapfixes.MapfixList{}
|
||||
resp.Mapfixes = make([]*mapfixes.MapfixResponse, len(items))
|
||||
for i, item := range items {
|
||||
var validated_asset_id *uint64
|
||||
if item.ValidatedAssetID != 0 {
|
||||
validated_asset_id = &item.ValidatedAssetID
|
||||
}
|
||||
var validated_asset_version *uint64
|
||||
if item.ValidatedAssetVersion != 0 {
|
||||
validated_asset_version = &item.ValidatedAssetVersion
|
||||
}
|
||||
resp.Mapfixes[i] = &mapfixes.MapfixResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
AssetID: item.AssetID,
|
||||
ValidatedAssetID: validated_asset_id,
|
||||
ValidatedAssetVersion: validated_asset_version,
|
||||
TargetAssetID: item.TargetAssetID,
|
||||
StatusID: mapfixes.MapfixStatus(item.StatusID),
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
func (svc *Mapfixes) List(ctx context.Context, request *mapfixes.ListRequest) (*mapfixes.MapfixList, error) {
|
||||
if request.Page == nil {
|
||||
return nil, PageError
|
||||
}
|
||||
|
||||
filter := service.NewMapfixFilter()
|
||||
if request.Filter != nil {
|
||||
if request.Filter.DisplayName != nil {
|
||||
filter.SetDisplayName(*request.Filter.DisplayName)
|
||||
}
|
||||
if request.Filter.Creator != nil {
|
||||
filter.SetCreator(*request.Filter.Creator)
|
||||
}
|
||||
if request.Filter.GameID != nil {
|
||||
filter.SetGameID(*request.Filter.GameID)
|
||||
}
|
||||
if request.Filter.Submitter != nil {
|
||||
filter.SetSubmitter(*request.Filter.Submitter)
|
||||
}
|
||||
}
|
||||
|
||||
items, err := svc.inner.ListMapfixes(ctx, filter, model.Page{
|
||||
Number: int32(request.Page.Number),
|
||||
Size: int32(request.Page.Size),
|
||||
}, datastore.ListSortDateDescending)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := mapfixes.MapfixList{}
|
||||
resp.Mapfixes = make([]*mapfixes.MapfixResponse, len(items))
|
||||
for i, item := range items {
|
||||
var validated_asset_id *uint64
|
||||
if item.ValidatedAssetID != 0 {
|
||||
validated_asset_id = &item.ValidatedAssetID
|
||||
}
|
||||
var validated_asset_version *uint64
|
||||
if item.ValidatedAssetVersion != 0 {
|
||||
validated_asset_version = &item.ValidatedAssetVersion
|
||||
}
|
||||
resp.Mapfixes[i] = &mapfixes.MapfixResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
AssetID: item.AssetID,
|
||||
ValidatedAssetID: validated_asset_id,
|
||||
ValidatedAssetVersion: validated_asset_version,
|
||||
TargetAssetID: item.TargetAssetID,
|
||||
StatusID: mapfixes.MapfixStatus(item.StatusID),
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
161
pkg/controller/submissions.go
Normal file
161
pkg/controller/submissions.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.itzana.me/strafesnet/go-grpc/submissions"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/datastore"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/model"
|
||||
"git.itzana.me/strafesnet/maps-service/pkg/service"
|
||||
)
|
||||
|
||||
type Submissions struct {
|
||||
*submissions.UnimplementedSubmissionsServiceServer
|
||||
inner *service.Service
|
||||
}
|
||||
|
||||
func NewSubmissionsController(
|
||||
inner *service.Service,
|
||||
) Submissions {
|
||||
return Submissions{
|
||||
inner: inner,
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *Submissions) Get(ctx context.Context, request *submissions.SubmissionId) (*submissions.SubmissionResponse, error) {
|
||||
item, err := svc.inner.GetSubmission(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var validated_asset_id *uint64
|
||||
if item.ValidatedAssetID != 0 {
|
||||
validated_asset_id = &item.ValidatedAssetID
|
||||
}
|
||||
var validated_asset_version *uint64
|
||||
if item.ValidatedAssetVersion != 0 {
|
||||
validated_asset_version = &item.ValidatedAssetVersion
|
||||
}
|
||||
var uploaded_asset_id *uint64
|
||||
if item.UploadedAssetID != 0 {
|
||||
uploaded_asset_id = &item.UploadedAssetID
|
||||
}
|
||||
return &submissions.SubmissionResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
AssetID: item.AssetID,
|
||||
ValidatedAssetID: validated_asset_id,
|
||||
ValidatedAssetVersion: validated_asset_version,
|
||||
UploadedAssetID: uploaded_asset_id,
|
||||
StatusID: submissions.SubmissionStatus(item.StatusID),
|
||||
}, nil
|
||||
}
|
||||
func (svc *Submissions) GetList(ctx context.Context, request *submissions.SubmissionIdList) (*submissions.SubmissionList, error) {
|
||||
items, err := svc.inner.GetSubmissionList(ctx, request.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := submissions.SubmissionList{}
|
||||
resp.Submissions = make([]*submissions.SubmissionResponse, len(items))
|
||||
for i, item := range items {
|
||||
var validated_asset_id *uint64
|
||||
if item.ValidatedAssetID != 0 {
|
||||
validated_asset_id = &item.ValidatedAssetID
|
||||
}
|
||||
var validated_asset_version *uint64
|
||||
if item.ValidatedAssetVersion != 0 {
|
||||
validated_asset_version = &item.ValidatedAssetVersion
|
||||
}
|
||||
var uploaded_asset_id *uint64
|
||||
if item.UploadedAssetID != 0 {
|
||||
uploaded_asset_id = &item.UploadedAssetID
|
||||
}
|
||||
resp.Submissions[i] = &submissions.SubmissionResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
AssetID: item.AssetID,
|
||||
ValidatedAssetID: validated_asset_id,
|
||||
ValidatedAssetVersion: validated_asset_version,
|
||||
UploadedAssetID: uploaded_asset_id,
|
||||
StatusID: submissions.SubmissionStatus(item.StatusID),
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
func (svc *Submissions) List(ctx context.Context, request *submissions.ListRequest) (*submissions.SubmissionList, error) {
|
||||
if request.Page == nil {
|
||||
return nil, PageError
|
||||
}
|
||||
|
||||
filter := service.NewSubmissionFilter()
|
||||
if request.Filter != nil {
|
||||
if request.Filter.DisplayName != nil {
|
||||
filter.SetDisplayName(*request.Filter.DisplayName)
|
||||
}
|
||||
if request.Filter.Creator != nil {
|
||||
filter.SetCreator(*request.Filter.Creator)
|
||||
}
|
||||
if request.Filter.GameID != nil {
|
||||
filter.SetGameID(*request.Filter.GameID)
|
||||
}
|
||||
if request.Filter.Submitter != nil {
|
||||
filter.SetSubmitter(*request.Filter.Submitter)
|
||||
}
|
||||
}
|
||||
|
||||
items, err := svc.inner.ListSubmissions(ctx, filter, model.Page{
|
||||
Number: int32(request.Page.Number),
|
||||
Size: int32(request.Page.Size),
|
||||
}, datastore.ListSortDateDescending)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := submissions.SubmissionList{}
|
||||
resp.Submissions = make([]*submissions.SubmissionResponse, len(items))
|
||||
for i, item := range items {
|
||||
var validated_asset_id *uint64
|
||||
if item.ValidatedAssetID != 0 {
|
||||
validated_asset_id = &item.ValidatedAssetID
|
||||
}
|
||||
var validated_asset_version *uint64
|
||||
if item.ValidatedAssetVersion != 0 {
|
||||
validated_asset_version = &item.ValidatedAssetVersion
|
||||
}
|
||||
var uploaded_asset_id *uint64
|
||||
if item.UploadedAssetID != 0 {
|
||||
uploaded_asset_id = &item.UploadedAssetID
|
||||
}
|
||||
resp.Submissions[i] = &submissions.SubmissionResponse{
|
||||
ID: item.ID,
|
||||
DisplayName: item.DisplayName,
|
||||
Creator: item.Creator,
|
||||
GameID: uint32(item.GameID),
|
||||
CreatedAt: item.CreatedAt.Unix(),
|
||||
UpdatedAt: item.UpdatedAt.Unix(),
|
||||
Submitter: uint64(item.Submitter),
|
||||
AssetVersion: uint64(item.AssetVersion),
|
||||
AssetID: item.AssetID,
|
||||
ValidatedAssetID: validated_asset_id,
|
||||
ValidatedAssetVersion: validated_asset_version,
|
||||
UploadedAssetID: uploaded_asset_id,
|
||||
StatusID: submissions.SubmissionStatus(item.StatusID),
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
@@ -103,6 +103,10 @@ func (svc *Service) GetMapfix(ctx context.Context, id int64) (model.Mapfix, erro
|
||||
return svc.db.Mapfixes().Get(ctx, id)
|
||||
}
|
||||
|
||||
func (svc *Service) GetMapfixList(ctx context.Context, ids []int64) ([]model.Mapfix, error) {
|
||||
return svc.db.Mapfixes().GetList(ctx, ids)
|
||||
}
|
||||
|
||||
func (svc *Service) UpdateMapfix(ctx context.Context, id int64, pmap MapfixUpdate) error {
|
||||
return svc.db.Mapfixes().Update(ctx, id, datastore.OptionalMap(pmap))
|
||||
}
|
||||
|
||||
@@ -559,7 +559,11 @@ func (svc *Service) ActionMapfixTriggerSubmit(ctx context.Context, params api.Ac
|
||||
target_status := model.MapfixStatusSubmitting
|
||||
update := service.NewMapfixUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
allow_statuses := []model.MapfixStatus{model.MapfixStatusUnderConstruction, model.MapfixStatusChangesRequested}
|
||||
allow_statuses := []model.MapfixStatus{
|
||||
model.MapfixStatusUnderConstruction,
|
||||
model.MapfixStatusChangesRequested,
|
||||
model.MapfixStatusSubmitted,
|
||||
}
|
||||
err = svc.inner.UpdateMapfixIfStatus(ctx, params.MapfixID, allow_statuses, update)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -556,7 +556,11 @@ func (svc *Service) ActionSubmissionTriggerSubmit(ctx context.Context, params ap
|
||||
target_status := model.SubmissionStatusSubmitting
|
||||
update := service.NewSubmissionUpdate()
|
||||
update.SetStatusID(target_status)
|
||||
allowed_statuses := []model.SubmissionStatus{model.SubmissionStatusUnderConstruction, model.SubmissionStatusChangesRequested}
|
||||
allowed_statuses := []model.SubmissionStatus{
|
||||
model.SubmissionStatusUnderConstruction,
|
||||
model.SubmissionStatusChangesRequested,
|
||||
model.SubmissionStatusSubmitted,
|
||||
}
|
||||
err = svc.inner.UpdateSubmissionIfStatus(ctx, params.SubmissionID, allowed_statuses, update)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -29,6 +29,13 @@ const ReviewActions = {
|
||||
confirmMessage: "Are you ready to submit this for review? The model version is locked in once submitted, but you can revoke it later if needed.",
|
||||
requiresConfirmation: true
|
||||
} as ReviewAction,
|
||||
Update: {
|
||||
name: "Update Model",
|
||||
action: "trigger-submit",
|
||||
confirmTitle: "Re-submit Latest Version",
|
||||
confirmMessage: "This action is equivalent to clicking Revoke and then clicking Submit.",
|
||||
requiresConfirmation: true
|
||||
} as ReviewAction,
|
||||
AdminSubmit: {
|
||||
name: "Submit on Behalf of User",
|
||||
action: "trigger-submit",
|
||||
@@ -183,6 +190,13 @@ const ReviewButtons: React.FC<ReviewButtonsProps> = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (status === Status.Submitted) {
|
||||
submitterButtons.push({
|
||||
action: ReviewActions.Update,
|
||||
color: "success",
|
||||
});
|
||||
}
|
||||
|
||||
if (StatusMatches(status, [Status.Submitted, Status.ChangesRequested])) {
|
||||
submitterButtons.push({
|
||||
action: ReviewActions.Revoke,
|
||||
|
||||
@@ -174,7 +174,7 @@ export default function ReviewerDashboardPage() {
|
||||
const [scriptPoliciesCount, setScriptPoliciesCount] = useState<number>(0);
|
||||
const [isLoadingScripts, setIsLoadingScripts] = useState(false);
|
||||
|
||||
// Fetch user roles
|
||||
// Fetch user roles
|
||||
useEffect(() => {
|
||||
// Fetch roles from API
|
||||
const controller = new AbortController();
|
||||
@@ -459,6 +459,9 @@ export default function ReviewerDashboardPage() {
|
||||
);
|
||||
const canReviewScripts = hasRole(userRoles, RolesConstants.ScriptWrite);
|
||||
|
||||
const tabIndexSubmissions = 0;
|
||||
const tabIndexMapfixes = canReviewSubmissions ? 1 : 0;
|
||||
|
||||
if (!hasAnyReviewerRole(userRoles)) {
|
||||
return (
|
||||
<Webpage>
|
||||
@@ -519,7 +522,7 @@ export default function ReviewerDashboardPage() {
|
||||
mb: 4
|
||||
}}>
|
||||
{canReviewSubmissions && (
|
||||
<Card>
|
||||
<Card onClick={()=>setTabValue(tabIndexSubmissions)}>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<AssignmentIcon sx={{ fontSize: 40, color: 'primary.main' }} />
|
||||
@@ -543,7 +546,7 @@ export default function ReviewerDashboardPage() {
|
||||
)}
|
||||
|
||||
{canReviewMapfixes && (
|
||||
<Card>
|
||||
<Card onClick={()=>setTabValue(tabIndexMapfixes)}>
|
||||
<CardContent>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<BuildIcon sx={{ fontSize: 40, color: 'secondary.main' }} />
|
||||
@@ -624,8 +627,8 @@ export default function ReviewerDashboardPage() {
|
||||
})()}
|
||||
</Box>
|
||||
}
|
||||
id="reviewer-tab-0"
|
||||
aria-controls="reviewer-tabpanel-0"
|
||||
id={`reviewer-tab-${tabIndexSubmissions}`}
|
||||
aria-controls={`reviewer-tabpanel-${tabIndexSubmissions}`}
|
||||
/>
|
||||
)}
|
||||
{canReviewMapfixes && (
|
||||
@@ -645,8 +648,8 @@ export default function ReviewerDashboardPage() {
|
||||
})()}
|
||||
</Box>
|
||||
}
|
||||
id={`reviewer-tab-${canReviewSubmissions ? 1 : 0}`}
|
||||
aria-controls={`reviewer-tabpanel-${canReviewSubmissions ? 1 : 0}`}
|
||||
id={`reviewer-tab-${tabIndexMapfixes}`}
|
||||
aria-controls={`reviewer-tabpanel-${tabIndexMapfixes}`}
|
||||
/>
|
||||
)}
|
||||
</Tabs>
|
||||
@@ -654,7 +657,7 @@ export default function ReviewerDashboardPage() {
|
||||
|
||||
{/* Submissions Tab */}
|
||||
{canReviewSubmissions && (
|
||||
<TabPanel value={tabValue} index={0}>
|
||||
<TabPanel value={tabValue} index={tabIndexSubmissions}>
|
||||
{userRoles && submissions && groupSubmissionsByStatus(submissions.Submissions, userRoles).reduce((sum, group) => sum + group.items.length, 0) === 0 ? (
|
||||
<Alert severity="success">
|
||||
No submissions currently need your review. Great job!
|
||||
@@ -740,7 +743,7 @@ export default function ReviewerDashboardPage() {
|
||||
|
||||
{/* Map Fixes Tab */}
|
||||
{canReviewMapfixes && (
|
||||
<TabPanel value={tabValue} index={canReviewSubmissions ? 1 : 0}>
|
||||
<TabPanel value={tabValue} index={tabIndexMapfixes}>
|
||||
{userRoles && mapfixes && groupMapfixesByStatus(mapfixes.Mapfixes, userRoles).reduce((sum, group) => sum + group.items.length, 0) === 0 ? (
|
||||
<Alert severity="success">
|
||||
No map fixes currently need your review. Great job!
|
||||
|
||||
Reference in New Issue
Block a user