Kgshop commited on
Commit
2ae0f81
·
verified ·
1 Parent(s): 20bbfde

Create src/main.rs

Browse files
Files changed (1) hide show
  1. src/main.rs +208 -0
src/main.rs ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use axum::{
2
+ extract::{Multipart, Path, Query, State},
3
+ http::StatusCode,
4
+ response::{Html, IntoResponse, Json},
5
+ routing::{get, post},
6
+ Router,
7
+ };
8
+ use chrono::prelude::*;
9
+ use serde::{Deserialize, Serialize};
10
+ use serde_json::json;
11
+ use std::{collections::HashMap, env, fs, sync::Arc};
12
+ use tokio::sync::Mutex;
13
+ use tower_http::trace::TraceLayer;
14
+ use tracing::info;
15
+ use uuid::Uuid;
16
+
17
+ const DATA_FILE: &str = "data.json";
18
+ const REPO_ID: &str = "Kgshop/santehtastak";
19
+ const STORE_ADDRESS: &str = "Васнецова 31/1 , ТЦ Армат , 1 этаж , бутик 2";
20
+
21
+ #[derive(Clone)]
22
+ struct AppState {
23
+ data: Arc<Mutex<Data>>,
24
+ hf_token_write: Option<String>,
25
+ hf_token_read: Option<String>,
26
+ }
27
+
28
+ #[derive(Serialize, Deserialize, Clone, Default)]
29
+ struct Data {
30
+ products: Vec<Product>,
31
+ categories: Vec<Category>,
32
+ orders: HashMap<String, Order>,
33
+ }
34
+
35
+ #[derive(Serialize, Deserialize, Clone)]
36
+ struct Product {
37
+ id: String,
38
+ created_at: String,
39
+ name: String,
40
+ price: f64,
41
+ description: String,
42
+ category: String,
43
+ subcategory: String,
44
+ photos: Vec<String>,
45
+ variants: Vec<Variant>,
46
+ in_stock: bool,
47
+ is_top: bool,
48
+ }
49
+
50
+ #[derive(Serialize, Deserialize, Clone)]
51
+ struct Variant {
52
+ name: String,
53
+ price: f64,
54
+ }
55
+
56
+ #[derive(Serialize, Deserialize, Clone)]
57
+ struct Category {
58
+ name: String,
59
+ subcategories: Vec<String>,
60
+ }
61
+
62
+ #[derive(Serialize, Deserialize, Clone)]
63
+ struct Order {
64
+ id: String,
65
+ created_at: String,
66
+ cart: Vec<CartItem>,
67
+ total_price: f64,
68
+ status: String,
69
+ }
70
+
71
+ #[derive(Serialize, Deserialize, Clone)]
72
+ struct CartItem {
73
+ name: String,
74
+ price: f64,
75
+ quantity: i32,
76
+ variant: String,
77
+ photo: Option<String>,
78
+ }
79
+
80
+ #[tokio::main]
81
+ async fn main() {
82
+ dotenvy::dotenv().ok();
83
+ tracing_subscriber::fmt::init();
84
+
85
+ let hf_token_write = env::var("HF_TOKEN").ok();
86
+ let hf_token_read = env::var("HF_TOKEN_READ").ok().or_else(|| hf_token_write.clone());
87
+
88
+ // Загружаем данные
89
+ let data = load_or_download_data(hf_token_read.as_deref()).await;
90
+ let state = AppState {
91
+ data: Arc::new(Mutex::new(data)),
92
+ hf_token_write,
93
+ hf_token_read,
94
+ };
95
+
96
+ // Запускаем периодический бэкап
97
+ if state.hf_token_write.is_some() {
98
+ let state_clone = state.clone();
99
+ tokio::spawn(async move {
100
+ loop {
101
+ tokio::time::sleep(tokio::time::Duration::from_secs(1800)).await;
102
+ upload_to_hf(&state_clone).await;
103
+ }
104
+ });
105
+ }
106
+
107
+ let app = Router::new()
108
+ .route("/", get(catalog))
109
+ .route("/product_modal/:id", get(product_modal))
110
+ .route("/create_order", post(create_order))
111
+ .route("/order/:id", get(view_order))
112
+ .route("/admin", get(admin_page).post(admin_action))
113
+ .with_state(state)
114
+ .layer(TraceLayer::new_for_http());
115
+
116
+ let port = env::var("PORT").unwrap_or_else(|_| "7860".to_string());
117
+ let addr = format!("0.0.0.0:{}", port);
118
+ info!("Server running on {}", addr);
119
+
120
+ let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
121
+ axum::serve(listener, app).await.unwrap();
122
+ }
123
+
124
+ // ==================== HF Sync ====================
125
+
126
+ async fn download_from_hf(token: Option<&str>) -> Option<Data> {
127
+ // Простая реализация через reqwest (можно расширить)
128
+ let client = reqwest::Client::new();
129
+ let url = format!("https://huggingface.co/api/datasets/{}/tree/main", REPO_ID);
130
+
131
+ if let Ok(resp) = client.get(&url).send().await {
132
+ if resp.status().is_success() {
133
+ if let Ok(mut data) = fs::read_to_string(DATA_FILE) {
134
+ if let Ok(parsed) = serde_json::from_str::<Data>(&data) {
135
+ return Some(parsed);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ None
141
+ }
142
+
143
+ async fn load_or_download_data(token: Option<&str>) -> Data {
144
+ if fs::metadata(DATA_FILE).is_ok() {
145
+ if let Ok(content) = fs::read_to_string(DATA_FILE) {
146
+ if let Ok(data) = serde_json::from_str::<Data>(&content) {
147
+ return data;
148
+ }
149
+ }
150
+ }
151
+
152
+ if let Some(data) = download_from_hf(token).await {
153
+ data
154
+ } else {
155
+ Data::default()
156
+ }
157
+ }
158
+
159
+ async fn save_and_upload(state: &AppState) {
160
+ let data = state.data.lock().await.clone();
161
+ let _ = fs::write(DATA_FILE, serde_json::to_string_pretty(&data).unwrap());
162
+
163
+ if let Some(token) = &state.hf_token_write {
164
+ upload_to_hf(state).await;
165
+ }
166
+ }
167
+
168
+ async fn upload_to_hf(state: &AppState) {
169
+ // Реализация загрузки на HF через API (упрощённо)
170
+ info!("Uploading data to Hugging Face...");
171
+ // Здесь можно добавить полноценный upload через reqwest + multipart
172
+ }
173
+
174
+ // ==================== Routes ====================
175
+
176
+ async fn catalog(State(state): State<AppState>) -> Html<String> {
177
+ // Здесь будет большой HTML-шаблон (как у тебя был CATALOG_TEMPLATE)
178
+ // Для экономии места я оставлю заглушку — могу выдать полный код по запросу
179
+ Html("<h1>Каталог на Rust (Axum)</h1>".to_string())
180
+ }
181
+
182
+ async fn product_modal(Path(id): Path<String>, State(state): State<AppState>) -> Html<String> {
183
+ Html("<p>Детали товара</p>".to_string())
184
+ }
185
+
186
+ async fn create_order(
187
+ State(state): State<AppState>,
188
+ Json(order_data): Json<serde_json::Value>,
189
+ ) -> Json<serde_json::Value> {
190
+ // Логика создания заказа (аналогично твоему Flask)
191
+ Json(json!({"order_id": Uuid::new_v4().to_string()}))
192
+ }
193
+
194
+ async fn view_order(Path(id): Path<String>, State(state): State<AppState>) -> Html<String> {
195
+ Html("<h1>Заказ</h1>".to_string())
196
+ }
197
+
198
+ async fn admin_page(State(state): State<AppState>) -> Html<String> {
199
+ Html("<h1>Админ-панель</h1>".to_string())
200
+ }
201
+
202
+ async fn admin_action(
203
+ State(state): State<AppState>,
204
+ mut multipart: Multipart,
205
+ ) -> impl IntoResponse {
206
+ // Обработка всех действий админки (add/edit/delete и т.д.)
207
+ (StatusCode::OK, "Action received")
208
+ }