ваше сообщение коммита

This commit is contained in:
2025-05-06 21:34:47 +03:00
parent bca410e099
commit 35ec814b19
11 changed files with 8204 additions and 140 deletions

View File

@@ -17,6 +17,8 @@ const identitiesRoutes = require('./routes/identities');
const chatRoutes = require('./routes/chat');
const adminRoutes = require('./routes/admin');
const tokensRouter = require('./routes/tokens');
const isicRoutes = require('./routes/isic'); // Добавленный импорт
const geocodingRoutes = require('./routes/geocoding'); // Добавленный импорт
const app = express();
@@ -114,6 +116,8 @@ app.use('/api/identities', identitiesRoutes);
app.use('/api/chat', chatRoutes);
app.use('/api/admin', adminRoutes);
app.use('/api/tokens', tokensRouter);
app.use('/api/isic', isicRoutes); // Добавленное использование роута
app.use('/api/geocoding', geocodingRoutes); // Добавленное использование роута
const nonceStore = new Map(); // или любая другая реализация хранилища nonce

View File

@@ -0,0 +1,5 @@
CodeLevel,LevelName
1,"Section"
2,"Division"
3,"Group"
4,"Class"
1 CodeLevel LevelName
2 1 Section
3 2 Division
4 3 Group
5 4 Class

View File

@@ -0,0 +1,767 @@
Sortorder,Code,CodeLevel,Level1,Level2,Level3,Level4,Level5,Level6
20,"A",1,"A",,,,,
30,"01",2,"A","01",,,,
40,"011",3,"A","01","011",,,
50,"0111",4,"A","01","011","0111",,
60,"0112",4,"A","01","011","0112",,
70,"0113",4,"A","01","011","0113",,
80,"0114",4,"A","01","011","0114",,
90,"0115",4,"A","01","011","0115",,
110,"0116",4,"A","01","011","0116",,
130,"0119",4,"A","01","011","0119",,
140,"012",3,"A","01","012",,,
150,"0121",4,"A","01","012","0121",,
160,"0122",4,"A","01","012","0122",,
170,"0123",4,"A","01","012","0123",,
180,"0124",4,"A","01","012","0124",,
190,"0125",4,"A","01","012","0125",,
200,"0126",4,"A","01","012","0126",,
210,"0127",4,"A","01","012","0127",,
220,"0128",4,"A","01","012","0128",,
230,"0129",4,"A","01","012","0129",,
240,"013",3,"A","01","013",,,
250,"0130",4,"A","01","013","0130",,
260,"014",3,"A","01","014",,,
270,"0141",4,"A","01","014","0141",,
280,"0142",4,"A","01","014","0142",,
290,"0143",4,"A","01","014","0143",,
300,"0144",4,"A","01","014","0144",,
310,"0145",4,"A","01","014","0145",,
320,"0146",4,"A","01","014","0146",,
330,"0149",4,"A","01","014","0149",,
340,"015",3,"A","01","015",,,
350,"0150",4,"A","01","015","0150",,
360,"016",3,"A","01","016",,,
370,"0161",4,"A","01","016","0161",,
380,"0162",4,"A","01","016","0162",,
390,"0163",4,"A","01","016","0163",,
400,"0164",4,"A","01","016","0164",,
410,"017",3,"A","01","017",,,
420,"0170",4,"A","01","017","0170",,
430,"02",2,"A","02",,,,
440,"021",3,"A","02","021",,,
450,"0210",4,"A","02","021","0210",,
460,"022",3,"A","02","022",,,
470,"0220",4,"A","02","022","0220",,
480,"023",3,"A","02","023",,,
490,"0230",4,"A","02","023","0230",,
500,"024",3,"A","02","024",,,
510,"0240",4,"A","02","024","0240",,
520,"03",2,"A","03",,,,
530,"031",3,"A","03","031",,,
540,"0311",4,"A","03","031","0311",,
550,"0312",4,"A","03","031","0312",,
560,"032",3,"A","03","032",,,
570,"0321",4,"A","03","032","0321",,
580,"0322",4,"A","03","032","0322",,
590,"B",1,"B",,,,,
600,"05",2,"B","05",,,,
610,"051",3,"B","05","051",,,
620,"0510",4,"B","05","051","0510",,
630,"052",3,"B","05","052",,,
640,"0520",4,"B","05","052","0520",,
650,"06",2,"B","06",,,,
660,"061",3,"B","06","061",,,
670,"0610",4,"B","06","061","0610",,
680,"062",3,"B","06","062",,,
690,"0620",4,"B","06","062","0620",,
700,"07",2,"B","07",,,,
710,"071",3,"B","07","071",,,
720,"0710",4,"B","07","071","0710",,
730,"072",3,"B","07","072",,,
740,"0721",4,"B","07","072","0721",,
750,"0729",4,"B","07","072","0729",,
760,"08",2,"B","08",,,,
770,"081",3,"B","08","081",,,
780,"0810",4,"B","08","081","0810",,
790,"089",3,"B","08","089",,,
800,"0891",4,"B","08","089","0891",,
810,"0892",4,"B","08","089","0892",,
820,"0893",4,"B","08","089","0893",,
830,"0899",4,"B","08","089","0899",,
840,"09",2,"B","09",,,,
850,"091",3,"B","09","091",,,
860,"0910",4,"B","09","091","0910",,
870,"099",3,"B","09","099",,,
880,"0990",4,"B","09","099","0990",,
890,"C",1,"C",,,,,
900,"10",2,"C","10",,,,
910,"101",3,"C","10","101",,,
920,"1010",4,"C","10","101","1010",,
930,"102",3,"C","10","102",,,
940,"1020",4,"C","10","102","1020",,
950,"103",3,"C","10","103",,,
960,"1030",4,"C","10","103","1030",,
970,"104",3,"C","10","104",,,
980,"1040",4,"C","10","104","1040",,
990,"105",3,"C","10","105",,,
1000,"1050",4,"C","10","105","1050",,
1010,"106",3,"C","10","106",,,
1020,"1061",4,"C","10","106","1061",,
1030,"1062",4,"C","10","106","1062",,
1040,"107",3,"C","10","107",,,
1050,"1071",4,"C","10","107","1071",,
1060,"1072",4,"C","10","107","1072",,
1070,"1073",4,"C","10","107","1073",,
1080,"1074",4,"C","10","107","1074",,
1090,"1075",4,"C","10","107","1075",,
1100,"1079",4,"C","10","107","1079",,
1110,"108",3,"C","10","108",,,
1120,"1080",4,"C","10","108","1080",,
1130,"11",2,"C","11",,,,
1140,"110",3,"C","11","110",,,
1150,"1101",4,"C","11","110","1101",,
1160,"1102",4,"C","11","110","1102",,
1170,"1103",4,"C","11","110","1103",,
1180,"1104",4,"C","11","110","1104",,
1190,"12",2,"C","12",,,,
1200,"120",3,"C","12","120",,,
1210,"1200",4,"C","12","120","1200",,
1220,"13",2,"C","13",,,,
1230,"131",3,"C","13","131",,,
1240,"1311",4,"C","13","131","1311",,
1250,"1312",4,"C","13","131","1312",,
1260,"1313",4,"C","13","131","1313",,
1270,"139",3,"C","13","139",,,
1280,"1391",4,"C","13","139","1391",,
1290,"1392",4,"C","13","139","1392",,
1300,"1393",4,"C","13","139","1393",,
1310,"1394",4,"C","13","139","1394",,
1320,"1399",4,"C","13","139","1399",,
1330,"14",2,"C","14",,,,
1340,"141",3,"C","14","141",,,
1350,"1410",4,"C","14","141","1410",,
1360,"142",3,"C","14","142",,,
1370,"1420",4,"C","14","142","1420",,
1380,"143",3,"C","14","143",,,
1390,"1430",4,"C","14","143","1430",,
1400,"15",2,"C","15",,,,
1410,"151",3,"C","15","151",,,
1420,"1511",4,"C","15","151","1511",,
1430,"1512",4,"C","15","151","1512",,
1440,"152",3,"C","15","152",,,
1450,"1520",4,"C","15","152","1520",,
1460,"16",2,"C","16",,,,
1470,"161",3,"C","16","161",,,
1480,"1610",4,"C","16","161","1610",,
1490,"162",3,"C","16","162",,,
1500,"1621",4,"C","16","162","1621",,
1510,"1622",4,"C","16","162","1622",,
1520,"1623",4,"C","16","162","1623",,
1530,"1629",4,"C","16","162","1629",,
1540,"17",2,"C","17",,,,
1550,"170",3,"C","17","170",,,
1560,"1701",4,"C","17","170","1701",,
1570,"1702",4,"C","17","170","1702",,
1580,"1709",4,"C","17","170","1709",,
1590,"18",2,"C","18",,,,
1600,"181",3,"C","18","181",,,
1610,"1811",4,"C","18","181","1811",,
1620,"1812",4,"C","18","181","1812",,
1630,"182",3,"C","18","182",,,
1640,"1820",4,"C","18","182","1820",,
1650,"19",2,"C","19",,,,
1660,"191",3,"C","19","191",,,
1670,"1910",4,"C","19","191","1910",,
1680,"192",3,"C","19","192",,,
1690,"1920",4,"C","19","192","1920",,
1700,"20",2,"C","20",,,,
1710,"201",3,"C","20","201",,,
1720,"2011",4,"C","20","201","2011",,
1730,"2012",4,"C","20","201","2012",,
1740,"2013",4,"C","20","201","2013",,
1750,"202",3,"C","20","202",,,
1760,"2021",4,"C","20","202","2021",,
1770,"2022",4,"C","20","202","2022",,
1780,"2023",4,"C","20","202","2023",,
1790,"2029",4,"C","20","202","2029",,
1800,"203",3,"C","20","203",,,
1810,"2030",4,"C","20","203","2030",,
1820,"21",2,"C","21",,,,
1830,"210",3,"C","21","210",,,
1840,"2100",4,"C","21","210","2100",,
1850,"22",2,"C","22",,,,
1860,"221",3,"C","22","221",,,
1870,"2211",4,"C","22","221","2211",,
1880,"2219",4,"C","22","221","2219",,
1890,"222",3,"C","22","222",,,
1900,"2220",4,"C","22","222","2220",,
1910,"23",2,"C","23",,,,
1920,"231",3,"C","23","231",,,
1930,"2310",4,"C","23","231","2310",,
1940,"239",3,"C","23","239",,,
1950,"2391",4,"C","23","239","2391",,
1960,"2392",4,"C","23","239","2392",,
1970,"2393",4,"C","23","239","2393",,
1980,"2394",4,"C","23","239","2394",,
1990,"2395",4,"C","23","239","2395",,
2000,"2396",4,"C","23","239","2396",,
2010,"2399",4,"C","23","239","2399",,
2020,"24",2,"C","24",,,,
2030,"241",3,"C","24","241",,,
2040,"2410",4,"C","24","241","2410",,
2050,"242",3,"C","24","242",,,
2060,"2420",4,"C","24","242","2420",,
2070,"243",3,"C","24","243",,,
2080,"2431",4,"C","24","243","2431",,
2090,"2432",4,"C","24","243","2432",,
2100,"25",2,"C","25",,,,
2110,"251",3,"C","25","251",,,
2120,"2511",4,"C","25","251","2511",,
2130,"2512",4,"C","25","251","2512",,
2140,"2513",4,"C","25","251","2513",,
2150,"252",3,"C","25","252",,,
2160,"2520",4,"C","25","252","2520",,
2170,"259",3,"C","25","259",,,
2180,"2591",4,"C","25","259","2591",,
2190,"2592",4,"C","25","259","2592",,
2200,"2593",4,"C","25","259","2593",,
2210,"2599",4,"C","25","259","2599",,
2220,"26",2,"C","26",,,,
2230,"261",3,"C","26","261",,,
2240,"2610",4,"C","26","261","2610",,
2250,"262",3,"C","26","262",,,
2260,"2620",4,"C","26","262","2620",,
2270,"263",3,"C","26","263",,,
2280,"2630",4,"C","26","263","2630",,
2290,"264",3,"C","26","264",,,
2300,"2640",4,"C","26","264","2640",,
2310,"265",3,"C","26","265",,,
2320,"2651",4,"C","26","265","2651",,
2330,"2652",4,"C","26","265","2652",,
2340,"266",3,"C","26","266",,,
2350,"2660",4,"C","26","266","2660",,
2360,"267",3,"C","26","267",,,
2370,"2670",4,"C","26","267","2670",,
2380,"268",3,"C","26","268",,,
2390,"2680",4,"C","26","268","2680",,
2400,"27",2,"C","27",,,,
2410,"271",3,"C","27","271",,,
2420,"2710",4,"C","27","271","2710",,
2430,"272",3,"C","27","272",,,
2440,"2720",4,"C","27","272","2720",,
2450,"273",3,"C","27","273",,,
2460,"2731",4,"C","27","273","2731",,
2470,"2732",4,"C","27","273","2732",,
2480,"2733",4,"C","27","273","2733",,
2490,"274",3,"C","27","274",,,
2500,"2740",4,"C","27","274","2740",,
2510,"275",3,"C","27","275",,,
2520,"2750",4,"C","27","275","2750",,
2530,"279",3,"C","27","279",,,
2540,"2790",4,"C","27","279","2790",,
2550,"28",2,"C","28",,,,
2560,"281",3,"C","28","281",,,
2570,"2811",4,"C","28","281","2811",,
2580,"2812",4,"C","28","281","2812",,
2590,"2813",4,"C","28","281","2813",,
2600,"2814",4,"C","28","281","2814",,
2610,"2815",4,"C","28","281","2815",,
2620,"2816",4,"C","28","281","2816",,
2630,"2817",4,"C","28","281","2817",,
2640,"2818",4,"C","28","281","2818",,
2650,"2819",4,"C","28","281","2819",,
2660,"282",3,"C","28","282",,,
2670,"2821",4,"C","28","282","2821",,
2680,"2822",4,"C","28","282","2822",,
2690,"2823",4,"C","28","282","2823",,
2700,"2824",4,"C","28","282","2824",,
2710,"2825",4,"C","28","282","2825",,
2720,"2826",4,"C","28","282","2826",,
2730,"2829",4,"C","28","282","2829",,
2740,"29",2,"C","29",,,,
2750,"291",3,"C","29","291",,,
2760,"2910",4,"C","29","291","2910",,
2770,"292",3,"C","29","292",,,
2780,"2920",4,"C","29","292","2920",,
2790,"293",3,"C","29","293",,,
2800,"2930",4,"C","29","293","2930",,
2810,"30",2,"C","30",,,,
2820,"301",3,"C","30","301",,,
2830,"3011",4,"C","30","301","3011",,
2840,"3012",4,"C","30","301","3012",,
2850,"302",3,"C","30","302",,,
2860,"3020",4,"C","30","302","3020",,
2870,"303",3,"C","30","303",,,
2880,"3030",4,"C","30","303","3030",,
2890,"304",3,"C","30","304",,,
2900,"3040",4,"C","30","304","3040",,
2910,"309",3,"C","30","309",,,
2920,"3091",4,"C","30","309","3091",,
2930,"3092",4,"C","30","309","3092",,
2940,"3099",4,"C","30","309","3099",,
2950,"31",2,"C","31",,,,
2960,"310",3,"C","31","310",,,
2970,"3100",4,"C","31","310","3100",,
2980,"32",2,"C","32",,,,
2990,"321",3,"C","32","321",,,
3000,"3211",4,"C","32","321","3211",,
3010,"3212",4,"C","32","321","3212",,
3020,"322",3,"C","32","322",,,
3030,"3220",4,"C","32","322","3220",,
3040,"323",3,"C","32","323",,,
3050,"3230",4,"C","32","323","3230",,
3060,"324",3,"C","32","324",,,
3070,"3240",4,"C","32","324","3240",,
3080,"325",3,"C","32","325",,,
3090,"3250",4,"C","32","325","3250",,
3100,"329",3,"C","32","329",,,
3110,"3290",4,"C","32","329","3290",,
3120,"33",2,"C","33",,,,
3130,"331",3,"C","33","331",,,
3140,"3311",4,"C","33","331","3311",,
3150,"3312",4,"C","33","331","3312",,
3160,"3313",4,"C","33","331","3313",,
3170,"3314",4,"C","33","331","3314",,
3180,"3315",4,"C","33","331","3315",,
3190,"3319",4,"C","33","331","3319",,
3200,"332",3,"C","33","332",,,
3210,"3320",4,"C","33","332","3320",,
3220,"D",1,"D",,,,,
3230,"35",2,"D","35",,,,
3240,"351",3,"D","35","351",,,
3250,"3510",4,"D","35","351","3510",,
3260,"352",3,"D","35","352",,,
3270,"3520",4,"D","35","352","3520",,
3280,"353",3,"D","35","353",,,
3290,"3530",4,"D","35","353","3530",,
3300,"E",1,"E",,,,,
3310,"36",2,"E","36",,,,
3320,"360",3,"E","36","360",,,
3330,"3600",4,"E","36","360","3600",,
3340,"37",2,"E","37",,,,
3350,"370",3,"E","37","370",,,
3360,"3700",4,"E","37","370","3700",,
3370,"38",2,"E","38",,,,
3380,"381",3,"E","38","381",,,
3390,"3811",4,"E","38","381","3811",,
3400,"3812",4,"E","38","381","3812",,
3410,"382",3,"E","38","382",,,
3420,"3821",4,"E","38","382","3821",,
3430,"3822",4,"E","38","382","3822",,
3440,"383",3,"E","38","383",,,
3450,"3830",4,"E","38","383","3830",,
3460,"39",2,"E","39",,,,
3470,"390",3,"E","39","390",,,
3480,"3900",4,"E","39","390","3900",,
3490,"F",1,"F",,,,,
3500,"41",2,"F","41",,,,
3510,"410",3,"F","41","410",,,
3520,"4100",4,"F","41","410","4100",,
3530,"42",2,"F","42",,,,
3540,"421",3,"F","42","421",,,
3550,"4210",4,"F","42","421","4210",,
3560,"422",3,"F","42","422",,,
3570,"4220",4,"F","42","422","4220",,
3580,"429",3,"F","42","429",,,
3590,"4290",4,"F","42","429","4290",,
3600,"43",2,"F","43",,,,
3610,"431",3,"F","43","431",,,
3620,"4311",4,"F","43","431","4311",,
3630,"4312",4,"F","43","431","4312",,
3640,"432",3,"F","43","432",,,
3650,"4321",4,"F","43","432","4321",,
3660,"4322",4,"F","43","432","4322",,
3670,"4329",4,"F","43","432","4329",,
3680,"433",3,"F","43","433",,,
3690,"4330",4,"F","43","433","4330",,
3700,"439",3,"F","43","439",,,
3710,"4390",4,"F","43","439","4390",,
3720,"G",1,"G",,,,,
3730,"45",2,"G","45",,,,
3740,"451",3,"G","45","451",,,
3750,"4510",4,"G","45","451","4510",,
3760,"452",3,"G","45","452",,,
3770,"4520",4,"G","45","452","4520",,
3780,"453",3,"G","45","453",,,
3790,"4530",4,"G","45","453","4530",,
3800,"454",3,"G","45","454",,,
3810,"4540",4,"G","45","454","4540",,
3820,"46",2,"G","46",,,,
3830,"461",3,"G","46","461",,,
3840,"4610",4,"G","46","461","4610",,
3850,"462",3,"G","46","462",,,
3860,"4620",4,"G","46","462","4620",,
3870,"463",3,"G","46","463",,,
3880,"4630",4,"G","46","463","4630",,
3890,"464",3,"G","46","464",,,
3900,"4641",4,"G","46","464","4641",,
3910,"4649",4,"G","46","464","4649",,
3920,"465",3,"G","46","465",,,
3930,"4651",4,"G","46","465","4651",,
3940,"4652",4,"G","46","465","4652",,
3950,"4653",4,"G","46","465","4653",,
3960,"4659",4,"G","46","465","4659",,
3970,"466",3,"G","46","466",,,
3980,"4661",4,"G","46","466","4661",,
3990,"4662",4,"G","46","466","4662",,
4000,"4663",4,"G","46","466","4663",,
4010,"4669",4,"G","46","466","4669",,
4020,"469",3,"G","46","469",,,
4030,"4690",4,"G","46","469","4690",,
4040,"47",2,"G","47",,,,
4050,"471",3,"G","47","471",,,
4060,"4711",4,"G","47","471","4711",,
4070,"4719",4,"G","47","471","4719",,
4080,"472",3,"G","47","472",,,
4090,"4721",4,"G","47","472","4721",,
4100,"4722",4,"G","47","472","4722",,
4110,"4723",4,"G","47","472","4723",,
4120,"473",3,"G","47","473",,,
4130,"4730",4,"G","47","473","4730",,
4140,"474",3,"G","47","474",,,
4150,"4741",4,"G","47","474","4741",,
4160,"4742",4,"G","47","474","4742",,
4170,"475",3,"G","47","475",,,
4180,"4751",4,"G","47","475","4751",,
4190,"4752",4,"G","47","475","4752",,
4200,"4753",4,"G","47","475","4753",,
4210,"4759",4,"G","47","475","4759",,
4220,"476",3,"G","47","476",,,
4230,"4761",4,"G","47","476","4761",,
4240,"4762",4,"G","47","476","4762",,
4250,"4763",4,"G","47","476","4763",,
4260,"4764",4,"G","47","476","4764",,
4270,"477",3,"G","47","477",,,
4280,"4771",4,"G","47","477","4771",,
4290,"4772",4,"G","47","477","4772",,
4300,"4773",4,"G","47","477","4773",,
4310,"4774",4,"G","47","477","4774",,
4320,"478",3,"G","47","478",,,
4330,"4781",4,"G","47","478","4781",,
4340,"4782",4,"G","47","478","4782",,
4350,"4789",4,"G","47","478","4789",,
4360,"479",3,"G","47","479",,,
4370,"4791",4,"G","47","479","4791",,
4380,"4799",4,"G","47","479","4799",,
4390,"H",1,"H",,,,,
4400,"49",2,"H","49",,,,
4410,"491",3,"H","49","491",,,
4420,"4911",4,"H","49","491","4911",,
4430,"4912",4,"H","49","491","4912",,
4440,"492",3,"H","49","492",,,
4450,"4921",4,"H","49","492","4921",,
4460,"4922",4,"H","49","492","4922",,
4470,"4923",4,"H","49","492","4923",,
4480,"493",3,"H","49","493",,,
4490,"4930",4,"H","49","493","4930",,
4500,"50",2,"H","50",,,,
4510,"501",3,"H","50","501",,,
4520,"5011",4,"H","50","501","5011",,
4530,"5012",4,"H","50","501","5012",,
4540,"502",3,"H","50","502",,,
4550,"5021",4,"H","50","502","5021",,
4560,"5022",4,"H","50","502","5022",,
4570,"51",2,"H","51",,,,
4580,"511",3,"H","51","511",,,
4590,"5110",4,"H","51","511","5110",,
4600,"512",3,"H","51","512",,,
4610,"5120",4,"H","51","512","5120",,
4620,"52",2,"H","52",,,,
4630,"521",3,"H","52","521",,,
4640,"5210",4,"H","52","521","5210",,
4650,"522",3,"H","52","522",,,
4660,"5221",4,"H","52","522","5221",,
4670,"5222",4,"H","52","522","5222",,
4680,"5223",4,"H","52","522","5223",,
4690,"5224",4,"H","52","522","5224",,
4700,"5229",4,"H","52","522","5229",,
4710,"53",2,"H","53",,,,
4720,"531",3,"H","53","531",,,
4730,"5310",4,"H","53","531","5310",,
4740,"532",3,"H","53","532",,,
4750,"5320",4,"H","53","532","5320",,
4760,"I",1,"I",,,,,
4770,"55",2,"I","55",,,,
4780,"551",3,"I","55","551",,,
4790,"5510",4,"I","55","551","5510",,
4800,"552",3,"I","55","552",,,
4810,"5520",4,"I","55","552","5520",,
4820,"559",3,"I","55","559",,,
4830,"5590",4,"I","55","559","5590",,
4840,"56",2,"I","56",,,,
4850,"561",3,"I","56","561",,,
4860,"5610",4,"I","56","561","5610",,
4870,"562",3,"I","56","562",,,
4880,"5621",4,"I","56","562","5621",,
4890,"5629",4,"I","56","562","5629",,
4930,"563",3,"I","56","563",,,
4940,"5630",4,"I","56","563","5630",,
4950,"J",1,"J",,,,,
4960,"58",2,"J","58",,,,
4970,"581",3,"J","58","581",,,
4980,"5811",4,"J","58","581","5811",,
4990,"5812",4,"J","58","581","5812",,
5000,"5813",4,"J","58","581","5813",,
5010,"5819",4,"J","58","581","5819",,
5020,"582",3,"J","58","582",,,
5030,"5820",4,"J","58","582","5820",,
5040,"59",2,"J","59",,,,
5050,"591",3,"J","59","591",,,
5060,"5911",4,"J","59","591","5911",,
5070,"5912",4,"J","59","591","5912",,
5080,"5913",4,"J","59","591","5913",,
5090,"5914",4,"J","59","591","5914",,
5100,"592",3,"J","59","592",,,
5110,"5920",4,"J","59","592","5920",,
5120,"60",2,"J","60",,,,
5130,"601",3,"J","60","601",,,
5140,"6010",4,"J","60","601","6010",,
5150,"602",3,"J","60","602",,,
5160,"6020",4,"J","60","602","6020",,
5180,"61",2,"J","61",,,,
5190,"611",3,"J","61","611",,,
5200,"6110",4,"J","61","611","6110",,
5210,"612",3,"J","61","612",,,
5220,"6120",4,"J","61","612","6120",,
5230,"613",3,"J","61","613",,,
5240,"6130",4,"J","61","613","6130",,
5250,"619",3,"J","61","619",,,
5260,"6190",4,"J","61","619","6190",,
5270,"62",2,"J","62",,,,
5280,"620",3,"J","62","620",,,
5290,"6201",4,"J","62","620","6201",,
5300,"6202",4,"J","62","620","6202",,
5310,"6209",4,"J","62","620","6209",,
5320,"63",2,"J","63",,,,
5330,"631",3,"J","63","631",,,
5340,"6311",4,"J","63","631","6311",,
5350,"6312",4,"J","63","631","6312",,
5360,"639",3,"J","63","639",,,
5370,"6391",4,"J","63","639","6391",,
5380,"6399",4,"J","63","639","6399",,
5390,"K",1,"K",,,,,
5400,"64",2,"K","64",,,,
5410,"641",3,"K","64","641",,,
5420,"6411",4,"K","64","641","6411",,
5430,"6419",4,"K","64","641","6419",,
5440,"642",3,"K","64","642",,,
5450,"6420",4,"K","64","642","6420",,
5460,"643",3,"K","64","643",,,
5470,"6430",4,"K","64","643","6430",,
5480,"649",3,"K","64","649",,,
5490,"6491",4,"K","64","649","6491",,
5500,"6492",4,"K","64","649","6492",,
5510,"6499",4,"K","64","649","6499",,
5520,"65",2,"K","65",,,,
5530,"651",3,"K","65","651",,,
5540,"6511",4,"K","65","651","6511",,
5550,"6512",4,"K","65","651","6512",,
5560,"652",3,"K","65","652",,,
5570,"6520",4,"K","65","652","6520",,
5580,"653",3,"K","65","653",,,
5590,"6530",4,"K","65","653","6530",,
5600,"66",2,"K","66",,,,
5610,"661",3,"K","66","661",,,
5620,"6611",4,"K","66","661","6611",,
5630,"6612",4,"K","66","661","6612",,
5640,"6619",4,"K","66","661","6619",,
5650,"662",3,"K","66","662",,,
5660,"6621",4,"K","66","662","6621",,
5670,"6622",4,"K","66","662","6622",,
5680,"6629",4,"K","66","662","6629",,
5690,"663",3,"K","66","663",,,
5700,"6630",4,"K","66","663","6630",,
5710,"L",1,"L",,,,,
5720,"68",2,"L","68",,,,
5730,"681",3,"L","68","681",,,
5740,"6810",4,"L","68","681","6810",,
5750,"682",3,"L","68","682",,,
5760,"6820",4,"L","68","682","6820",,
5770,"M",1,"M",,,,,
5780,"69",2,"M","69",,,,
5790,"691",3,"M","69","691",,,
5800,"6910",4,"M","69","691","6910",,
5810,"692",3,"M","69","692",,,
5820,"6920",4,"M","69","692","6920",,
5830,"70",2,"M","70",,,,
5840,"701",3,"M","70","701",,,
5850,"7010",4,"M","70","701","7010",,
5860,"702",3,"M","70","702",,,
5870,"7020",4,"M","70","702","7020",,
5880,"71",2,"M","71",,,,
5890,"711",3,"M","71","711",,,
5900,"7110",4,"M","71","711","7110",,
5910,"712",3,"M","71","712",,,
5920,"7120",4,"M","71","712","7120",,
5930,"72",2,"M","72",,,,
5940,"721",3,"M","72","721",,,
5960,"7210",4,"M","72","721","7210",,
5970,"722",3,"M","72","722",,,
5980,"7220",4,"M","72","722","7220",,
5990,"73",2,"M","73",,,,
6000,"731",3,"M","73","731",,,
6010,"7310",4,"M","73","731","7310",,
6020,"732",3,"M","73","732",,,
6030,"7320",4,"M","73","732","7320",,
6040,"74",2,"M","74",,,,
6050,"741",3,"M","74","741",,,
6060,"7410",4,"M","74","741","7410",,
6070,"742",3,"M","74","742",,,
6080,"7420",4,"M","74","742","7420",,
6090,"749",3,"M","74","749",,,
6100,"7490",4,"M","74","749","7490",,
6110,"75",2,"M","75",,,,
6120,"750",3,"M","75","750",,,
6130,"7500",4,"M","75","750","7500",,
6140,"N",1,"N",,,,,
6150,"77",2,"N","77",,,,
6160,"771",3,"N","77","771",,,
6170,"7710",4,"N","77","771","7710",,
6180,"772",3,"N","77","772",,,
6190,"7721",4,"N","77","772","7721",,
6200,"7722",4,"N","77","772","7722",,
6210,"7729",4,"N","77","772","7729",,
6220,"773",3,"N","77","773",,,
6230,"7730",4,"N","77","773","7730",,
6240,"774",3,"N","77","774",,,
6250,"7740",4,"N","77","774","7740",,
6260,"78",2,"N","78",,,,
6270,"781",3,"N","78","781",,,
6280,"7810",4,"N","78","781","7810",,
6290,"782",3,"N","78","782",,,
6300,"7820",4,"N","78","782","7820",,
6310,"783",3,"N","78","783",,,
6320,"7830",4,"N","78","783","7830",,
6330,"79",2,"N","79",,,,
6340,"791",3,"N","79","791",,,
6350,"7911",4,"N","79","791","7911",,
6360,"7912",4,"N","79","791","7912",,
6370,"799",3,"N","79","799",,,
6380,"7990",4,"N","79","799","7990",,
6390,"80",2,"N","80",,,,
6400,"801",3,"N","80","801",,,
6410,"8010",4,"N","80","801","8010",,
6420,"802",3,"N","80","802",,,
6430,"8020",4,"N","80","802","8020",,
6440,"803",3,"N","80","803",,,
6450,"8030",4,"N","80","803","8030",,
6460,"81",2,"N","81",,,,
6470,"811",3,"N","81","811",,,
6480,"8110",4,"N","81","811","8110",,
6490,"812",3,"N","81","812",,,
6500,"8121",4,"N","81","812","8121",,
6510,"8129",4,"N","81","812","8129",,
6520,"813",3,"N","81","813",,,
6530,"8130",4,"N","81","813","8130",,
6540,"82",2,"N","82",,,,
6550,"821",3,"N","82","821",,,
6560,"8211",4,"N","82","821","8211",,
6570,"8219",4,"N","82","821","8219",,
6580,"822",3,"N","82","822",,,
6590,"8220",4,"N","82","822","8220",,
6600,"823",3,"N","82","823",,,
6610,"8230",4,"N","82","823","8230",,
6620,"829",3,"N","82","829",,,
6630,"8291",4,"N","82","829","8291",,
6640,"8292",4,"N","82","829","8292",,
6650,"8299",4,"N","82","829","8299",,
6660,"O",1,"O",,,,,
6670,"84",2,"O","84",,,,
6680,"841",3,"O","84","841",,,
6690,"8411",4,"O","84","841","8411",,
6700,"8412",4,"O","84","841","8412",,
6710,"8413",4,"O","84","841","8413",,
6730,"842",3,"O","84","842",,,
6740,"8421",4,"O","84","842","8421",,
6750,"8422",4,"O","84","842","8422",,
6760,"8423",4,"O","84","842","8423",,
6770,"843",3,"O","84","843",,,
6780,"8430",4,"O","84","843","8430",,
6790,"P",1,"P",,,,,
6800,"85",2,"P","85",,,,
6810,"851",3,"P","85","851",,,
6820,"8510",4,"P","85","851","8510",,
6830,"852",3,"P","85","852",,,
6840,"8521",4,"P","85","852","8521",,
6850,"8522",4,"P","85","852","8522",,
6860,"853",3,"P","85","853",,,
6870,"8530",4,"P","85","853","8530",,
6880,"854",3,"P","85","854",,,
6890,"8541",4,"P","85","854","8541",,
6900,"8542",4,"P","85","854","8542",,
6910,"8549",4,"P","85","854","8549",,
6920,"855",3,"P","85","855",,,
6930,"8550",4,"P","85","855","8550",,
6940,"Q",1,"Q",,,,,
6950,"86",2,"Q","86",,,,
6960,"861",3,"Q","86","861",,,
6970,"8610",4,"Q","86","861","8610",,
6980,"862",3,"Q","86","862",,,
6990,"8620",4,"Q","86","862","8620",,
7000,"869",3,"Q","86","869",,,
7010,"8690",4,"Q","86","869","8690",,
7020,"87",2,"Q","87",,,,
7030,"871",3,"Q","87","871",,,
7040,"8710",4,"Q","87","871","8710",,
7050,"872",3,"Q","87","872",,,
7060,"8720",4,"Q","87","872","8720",,
7070,"873",3,"Q","87","873",,,
7080,"8730",4,"Q","87","873","8730",,
7090,"879",3,"Q","87","879",,,
7100,"8790",4,"Q","87","879","8790",,
7110,"88",2,"Q","88",,,,
7120,"881",3,"Q","88","881",,,
7130,"8810",4,"Q","88","881","8810",,
7140,"889",3,"Q","88","889",,,
7150,"8890",4,"Q","88","889","8890",,
7160,"R",1,"R",,,,,
7170,"90",2,"R","90",,,,
7180,"900",3,"R","90","900",,,
7190,"9000",4,"R","90","900","9000",,
7200,"91",2,"R","91",,,,
7210,"910",3,"R","91","910",,,
7215,"9101",4,"R","91","910","9101",,
7220,"9102",4,"R","91","910","9102",,
7230,"9103",4,"R","91","910","9103",,
7240,"92",2,"R","92",,,,
7250,"920",3,"R","92","920",,,
7260,"9200",4,"R","92","920","9200",,
7270,"93",2,"R","93",,,,
7280,"931",3,"R","93","931",,,
7290,"9311",4,"R","93","931","9311",,
7300,"9312",4,"R","93","931","9312",,
7310,"9319",4,"R","93","931","9319",,
7320,"932",3,"R","93","932",,,
7330,"9321",4,"R","93","932","9321",,
7340,"9329",4,"R","93","932","9329",,
7350,"S",1,"S",,,,,
7360,"94",2,"S","94",,,,
7370,"941",3,"S","94","941",,,
7380,"9411",4,"S","94","941","9411",,
7390,"9412",4,"S","94","941","9412",,
7400,"942",3,"S","94","942",,,
7410,"9420",4,"S","94","942","9420",,
7420,"949",3,"S","94","949",,,
7430,"9491",4,"S","94","949","9491",,
7440,"9492",4,"S","94","949","9492",,
7450,"9499",4,"S","94","949","9499",,
7460,"95",2,"S","95",,,,
7470,"951",3,"S","95","951",,,
7480,"9511",4,"S","95","951","9511",,
7490,"9512",4,"S","95","951","9512",,
7500,"952",3,"S","95","952",,,
7510,"9521",4,"S","95","952","9521",,
7520,"9522",4,"S","95","952","9522",,
7530,"9523",4,"S","95","952","9523",,
7540,"9524",4,"S","95","952","9524",,
7550,"9529",4,"S","95","952","9529",,
7560,"96",2,"S","96",,,,
7570,"960",3,"S","96","960",,,
7580,"9601",4,"S","96","960","9601",,
7590,"9602",4,"S","96","960","9602",,
7600,"9603",4,"S","96","960","9603",,
7610,"9609",4,"S","96","960","9609",,
7620,"T",1,"T",,,,,
7630,"97",2,"T","97",,,,
7640,"970",3,"T","97","970",,,
7650,"9700",4,"T","97","970","9700",,
7660,"98",2,"T","98",,,,
7670,"981",3,"T","98","981",,,
7680,"9810",4,"T","98","981","9810",,
7690,"982",3,"T","98","982",,,
7700,"9820",4,"T","98","982","9820",,
7710,"U",1,"U",,,,,
7720,"99",2,"U","99",,,,
7730,"990",3,"U","99","990",,,
7740,"9900",4,"U","99","990","9900",,
1 Sortorder Code CodeLevel Level1 Level2 Level3 Level4 Level5 Level6
2 20 A 1 A
3 30 01 2 A 01
4 40 011 3 A 01 011
5 50 0111 4 A 01 011 0111
6 60 0112 4 A 01 011 0112
7 70 0113 4 A 01 011 0113
8 80 0114 4 A 01 011 0114
9 90 0115 4 A 01 011 0115
10 110 0116 4 A 01 011 0116
11 130 0119 4 A 01 011 0119
12 140 012 3 A 01 012
13 150 0121 4 A 01 012 0121
14 160 0122 4 A 01 012 0122
15 170 0123 4 A 01 012 0123
16 180 0124 4 A 01 012 0124
17 190 0125 4 A 01 012 0125
18 200 0126 4 A 01 012 0126
19 210 0127 4 A 01 012 0127
20 220 0128 4 A 01 012 0128
21 230 0129 4 A 01 012 0129
22 240 013 3 A 01 013
23 250 0130 4 A 01 013 0130
24 260 014 3 A 01 014
25 270 0141 4 A 01 014 0141
26 280 0142 4 A 01 014 0142
27 290 0143 4 A 01 014 0143
28 300 0144 4 A 01 014 0144
29 310 0145 4 A 01 014 0145
30 320 0146 4 A 01 014 0146
31 330 0149 4 A 01 014 0149
32 340 015 3 A 01 015
33 350 0150 4 A 01 015 0150
34 360 016 3 A 01 016
35 370 0161 4 A 01 016 0161
36 380 0162 4 A 01 016 0162
37 390 0163 4 A 01 016 0163
38 400 0164 4 A 01 016 0164
39 410 017 3 A 01 017
40 420 0170 4 A 01 017 0170
41 430 02 2 A 02
42 440 021 3 A 02 021
43 450 0210 4 A 02 021 0210
44 460 022 3 A 02 022
45 470 0220 4 A 02 022 0220
46 480 023 3 A 02 023
47 490 0230 4 A 02 023 0230
48 500 024 3 A 02 024
49 510 0240 4 A 02 024 0240
50 520 03 2 A 03
51 530 031 3 A 03 031
52 540 0311 4 A 03 031 0311
53 550 0312 4 A 03 031 0312
54 560 032 3 A 03 032
55 570 0321 4 A 03 032 0321
56 580 0322 4 A 03 032 0322
57 590 B 1 B
58 600 05 2 B 05
59 610 051 3 B 05 051
60 620 0510 4 B 05 051 0510
61 630 052 3 B 05 052
62 640 0520 4 B 05 052 0520
63 650 06 2 B 06
64 660 061 3 B 06 061
65 670 0610 4 B 06 061 0610
66 680 062 3 B 06 062
67 690 0620 4 B 06 062 0620
68 700 07 2 B 07
69 710 071 3 B 07 071
70 720 0710 4 B 07 071 0710
71 730 072 3 B 07 072
72 740 0721 4 B 07 072 0721
73 750 0729 4 B 07 072 0729
74 760 08 2 B 08
75 770 081 3 B 08 081
76 780 0810 4 B 08 081 0810
77 790 089 3 B 08 089
78 800 0891 4 B 08 089 0891
79 810 0892 4 B 08 089 0892
80 820 0893 4 B 08 089 0893
81 830 0899 4 B 08 089 0899
82 840 09 2 B 09
83 850 091 3 B 09 091
84 860 0910 4 B 09 091 0910
85 870 099 3 B 09 099
86 880 0990 4 B 09 099 0990
87 890 C 1 C
88 900 10 2 C 10
89 910 101 3 C 10 101
90 920 1010 4 C 10 101 1010
91 930 102 3 C 10 102
92 940 1020 4 C 10 102 1020
93 950 103 3 C 10 103
94 960 1030 4 C 10 103 1030
95 970 104 3 C 10 104
96 980 1040 4 C 10 104 1040
97 990 105 3 C 10 105
98 1000 1050 4 C 10 105 1050
99 1010 106 3 C 10 106
100 1020 1061 4 C 10 106 1061
101 1030 1062 4 C 10 106 1062
102 1040 107 3 C 10 107
103 1050 1071 4 C 10 107 1071
104 1060 1072 4 C 10 107 1072
105 1070 1073 4 C 10 107 1073
106 1080 1074 4 C 10 107 1074
107 1090 1075 4 C 10 107 1075
108 1100 1079 4 C 10 107 1079
109 1110 108 3 C 10 108
110 1120 1080 4 C 10 108 1080
111 1130 11 2 C 11
112 1140 110 3 C 11 110
113 1150 1101 4 C 11 110 1101
114 1160 1102 4 C 11 110 1102
115 1170 1103 4 C 11 110 1103
116 1180 1104 4 C 11 110 1104
117 1190 12 2 C 12
118 1200 120 3 C 12 120
119 1210 1200 4 C 12 120 1200
120 1220 13 2 C 13
121 1230 131 3 C 13 131
122 1240 1311 4 C 13 131 1311
123 1250 1312 4 C 13 131 1312
124 1260 1313 4 C 13 131 1313
125 1270 139 3 C 13 139
126 1280 1391 4 C 13 139 1391
127 1290 1392 4 C 13 139 1392
128 1300 1393 4 C 13 139 1393
129 1310 1394 4 C 13 139 1394
130 1320 1399 4 C 13 139 1399
131 1330 14 2 C 14
132 1340 141 3 C 14 141
133 1350 1410 4 C 14 141 1410
134 1360 142 3 C 14 142
135 1370 1420 4 C 14 142 1420
136 1380 143 3 C 14 143
137 1390 1430 4 C 14 143 1430
138 1400 15 2 C 15
139 1410 151 3 C 15 151
140 1420 1511 4 C 15 151 1511
141 1430 1512 4 C 15 151 1512
142 1440 152 3 C 15 152
143 1450 1520 4 C 15 152 1520
144 1460 16 2 C 16
145 1470 161 3 C 16 161
146 1480 1610 4 C 16 161 1610
147 1490 162 3 C 16 162
148 1500 1621 4 C 16 162 1621
149 1510 1622 4 C 16 162 1622
150 1520 1623 4 C 16 162 1623
151 1530 1629 4 C 16 162 1629
152 1540 17 2 C 17
153 1550 170 3 C 17 170
154 1560 1701 4 C 17 170 1701
155 1570 1702 4 C 17 170 1702
156 1580 1709 4 C 17 170 1709
157 1590 18 2 C 18
158 1600 181 3 C 18 181
159 1610 1811 4 C 18 181 1811
160 1620 1812 4 C 18 181 1812
161 1630 182 3 C 18 182
162 1640 1820 4 C 18 182 1820
163 1650 19 2 C 19
164 1660 191 3 C 19 191
165 1670 1910 4 C 19 191 1910
166 1680 192 3 C 19 192
167 1690 1920 4 C 19 192 1920
168 1700 20 2 C 20
169 1710 201 3 C 20 201
170 1720 2011 4 C 20 201 2011
171 1730 2012 4 C 20 201 2012
172 1740 2013 4 C 20 201 2013
173 1750 202 3 C 20 202
174 1760 2021 4 C 20 202 2021
175 1770 2022 4 C 20 202 2022
176 1780 2023 4 C 20 202 2023
177 1790 2029 4 C 20 202 2029
178 1800 203 3 C 20 203
179 1810 2030 4 C 20 203 2030
180 1820 21 2 C 21
181 1830 210 3 C 21 210
182 1840 2100 4 C 21 210 2100
183 1850 22 2 C 22
184 1860 221 3 C 22 221
185 1870 2211 4 C 22 221 2211
186 1880 2219 4 C 22 221 2219
187 1890 222 3 C 22 222
188 1900 2220 4 C 22 222 2220
189 1910 23 2 C 23
190 1920 231 3 C 23 231
191 1930 2310 4 C 23 231 2310
192 1940 239 3 C 23 239
193 1950 2391 4 C 23 239 2391
194 1960 2392 4 C 23 239 2392
195 1970 2393 4 C 23 239 2393
196 1980 2394 4 C 23 239 2394
197 1990 2395 4 C 23 239 2395
198 2000 2396 4 C 23 239 2396
199 2010 2399 4 C 23 239 2399
200 2020 24 2 C 24
201 2030 241 3 C 24 241
202 2040 2410 4 C 24 241 2410
203 2050 242 3 C 24 242
204 2060 2420 4 C 24 242 2420
205 2070 243 3 C 24 243
206 2080 2431 4 C 24 243 2431
207 2090 2432 4 C 24 243 2432
208 2100 25 2 C 25
209 2110 251 3 C 25 251
210 2120 2511 4 C 25 251 2511
211 2130 2512 4 C 25 251 2512
212 2140 2513 4 C 25 251 2513
213 2150 252 3 C 25 252
214 2160 2520 4 C 25 252 2520
215 2170 259 3 C 25 259
216 2180 2591 4 C 25 259 2591
217 2190 2592 4 C 25 259 2592
218 2200 2593 4 C 25 259 2593
219 2210 2599 4 C 25 259 2599
220 2220 26 2 C 26
221 2230 261 3 C 26 261
222 2240 2610 4 C 26 261 2610
223 2250 262 3 C 26 262
224 2260 2620 4 C 26 262 2620
225 2270 263 3 C 26 263
226 2280 2630 4 C 26 263 2630
227 2290 264 3 C 26 264
228 2300 2640 4 C 26 264 2640
229 2310 265 3 C 26 265
230 2320 2651 4 C 26 265 2651
231 2330 2652 4 C 26 265 2652
232 2340 266 3 C 26 266
233 2350 2660 4 C 26 266 2660
234 2360 267 3 C 26 267
235 2370 2670 4 C 26 267 2670
236 2380 268 3 C 26 268
237 2390 2680 4 C 26 268 2680
238 2400 27 2 C 27
239 2410 271 3 C 27 271
240 2420 2710 4 C 27 271 2710
241 2430 272 3 C 27 272
242 2440 2720 4 C 27 272 2720
243 2450 273 3 C 27 273
244 2460 2731 4 C 27 273 2731
245 2470 2732 4 C 27 273 2732
246 2480 2733 4 C 27 273 2733
247 2490 274 3 C 27 274
248 2500 2740 4 C 27 274 2740
249 2510 275 3 C 27 275
250 2520 2750 4 C 27 275 2750
251 2530 279 3 C 27 279
252 2540 2790 4 C 27 279 2790
253 2550 28 2 C 28
254 2560 281 3 C 28 281
255 2570 2811 4 C 28 281 2811
256 2580 2812 4 C 28 281 2812
257 2590 2813 4 C 28 281 2813
258 2600 2814 4 C 28 281 2814
259 2610 2815 4 C 28 281 2815
260 2620 2816 4 C 28 281 2816
261 2630 2817 4 C 28 281 2817
262 2640 2818 4 C 28 281 2818
263 2650 2819 4 C 28 281 2819
264 2660 282 3 C 28 282
265 2670 2821 4 C 28 282 2821
266 2680 2822 4 C 28 282 2822
267 2690 2823 4 C 28 282 2823
268 2700 2824 4 C 28 282 2824
269 2710 2825 4 C 28 282 2825
270 2720 2826 4 C 28 282 2826
271 2730 2829 4 C 28 282 2829
272 2740 29 2 C 29
273 2750 291 3 C 29 291
274 2760 2910 4 C 29 291 2910
275 2770 292 3 C 29 292
276 2780 2920 4 C 29 292 2920
277 2790 293 3 C 29 293
278 2800 2930 4 C 29 293 2930
279 2810 30 2 C 30
280 2820 301 3 C 30 301
281 2830 3011 4 C 30 301 3011
282 2840 3012 4 C 30 301 3012
283 2850 302 3 C 30 302
284 2860 3020 4 C 30 302 3020
285 2870 303 3 C 30 303
286 2880 3030 4 C 30 303 3030
287 2890 304 3 C 30 304
288 2900 3040 4 C 30 304 3040
289 2910 309 3 C 30 309
290 2920 3091 4 C 30 309 3091
291 2930 3092 4 C 30 309 3092
292 2940 3099 4 C 30 309 3099
293 2950 31 2 C 31
294 2960 310 3 C 31 310
295 2970 3100 4 C 31 310 3100
296 2980 32 2 C 32
297 2990 321 3 C 32 321
298 3000 3211 4 C 32 321 3211
299 3010 3212 4 C 32 321 3212
300 3020 322 3 C 32 322
301 3030 3220 4 C 32 322 3220
302 3040 323 3 C 32 323
303 3050 3230 4 C 32 323 3230
304 3060 324 3 C 32 324
305 3070 3240 4 C 32 324 3240
306 3080 325 3 C 32 325
307 3090 3250 4 C 32 325 3250
308 3100 329 3 C 32 329
309 3110 3290 4 C 32 329 3290
310 3120 33 2 C 33
311 3130 331 3 C 33 331
312 3140 3311 4 C 33 331 3311
313 3150 3312 4 C 33 331 3312
314 3160 3313 4 C 33 331 3313
315 3170 3314 4 C 33 331 3314
316 3180 3315 4 C 33 331 3315
317 3190 3319 4 C 33 331 3319
318 3200 332 3 C 33 332
319 3210 3320 4 C 33 332 3320
320 3220 D 1 D
321 3230 35 2 D 35
322 3240 351 3 D 35 351
323 3250 3510 4 D 35 351 3510
324 3260 352 3 D 35 352
325 3270 3520 4 D 35 352 3520
326 3280 353 3 D 35 353
327 3290 3530 4 D 35 353 3530
328 3300 E 1 E
329 3310 36 2 E 36
330 3320 360 3 E 36 360
331 3330 3600 4 E 36 360 3600
332 3340 37 2 E 37
333 3350 370 3 E 37 370
334 3360 3700 4 E 37 370 3700
335 3370 38 2 E 38
336 3380 381 3 E 38 381
337 3390 3811 4 E 38 381 3811
338 3400 3812 4 E 38 381 3812
339 3410 382 3 E 38 382
340 3420 3821 4 E 38 382 3821
341 3430 3822 4 E 38 382 3822
342 3440 383 3 E 38 383
343 3450 3830 4 E 38 383 3830
344 3460 39 2 E 39
345 3470 390 3 E 39 390
346 3480 3900 4 E 39 390 3900
347 3490 F 1 F
348 3500 41 2 F 41
349 3510 410 3 F 41 410
350 3520 4100 4 F 41 410 4100
351 3530 42 2 F 42
352 3540 421 3 F 42 421
353 3550 4210 4 F 42 421 4210
354 3560 422 3 F 42 422
355 3570 4220 4 F 42 422 4220
356 3580 429 3 F 42 429
357 3590 4290 4 F 42 429 4290
358 3600 43 2 F 43
359 3610 431 3 F 43 431
360 3620 4311 4 F 43 431 4311
361 3630 4312 4 F 43 431 4312
362 3640 432 3 F 43 432
363 3650 4321 4 F 43 432 4321
364 3660 4322 4 F 43 432 4322
365 3670 4329 4 F 43 432 4329
366 3680 433 3 F 43 433
367 3690 4330 4 F 43 433 4330
368 3700 439 3 F 43 439
369 3710 4390 4 F 43 439 4390
370 3720 G 1 G
371 3730 45 2 G 45
372 3740 451 3 G 45 451
373 3750 4510 4 G 45 451 4510
374 3760 452 3 G 45 452
375 3770 4520 4 G 45 452 4520
376 3780 453 3 G 45 453
377 3790 4530 4 G 45 453 4530
378 3800 454 3 G 45 454
379 3810 4540 4 G 45 454 4540
380 3820 46 2 G 46
381 3830 461 3 G 46 461
382 3840 4610 4 G 46 461 4610
383 3850 462 3 G 46 462
384 3860 4620 4 G 46 462 4620
385 3870 463 3 G 46 463
386 3880 4630 4 G 46 463 4630
387 3890 464 3 G 46 464
388 3900 4641 4 G 46 464 4641
389 3910 4649 4 G 46 464 4649
390 3920 465 3 G 46 465
391 3930 4651 4 G 46 465 4651
392 3940 4652 4 G 46 465 4652
393 3950 4653 4 G 46 465 4653
394 3960 4659 4 G 46 465 4659
395 3970 466 3 G 46 466
396 3980 4661 4 G 46 466 4661
397 3990 4662 4 G 46 466 4662
398 4000 4663 4 G 46 466 4663
399 4010 4669 4 G 46 466 4669
400 4020 469 3 G 46 469
401 4030 4690 4 G 46 469 4690
402 4040 47 2 G 47
403 4050 471 3 G 47 471
404 4060 4711 4 G 47 471 4711
405 4070 4719 4 G 47 471 4719
406 4080 472 3 G 47 472
407 4090 4721 4 G 47 472 4721
408 4100 4722 4 G 47 472 4722
409 4110 4723 4 G 47 472 4723
410 4120 473 3 G 47 473
411 4130 4730 4 G 47 473 4730
412 4140 474 3 G 47 474
413 4150 4741 4 G 47 474 4741
414 4160 4742 4 G 47 474 4742
415 4170 475 3 G 47 475
416 4180 4751 4 G 47 475 4751
417 4190 4752 4 G 47 475 4752
418 4200 4753 4 G 47 475 4753
419 4210 4759 4 G 47 475 4759
420 4220 476 3 G 47 476
421 4230 4761 4 G 47 476 4761
422 4240 4762 4 G 47 476 4762
423 4250 4763 4 G 47 476 4763
424 4260 4764 4 G 47 476 4764
425 4270 477 3 G 47 477
426 4280 4771 4 G 47 477 4771
427 4290 4772 4 G 47 477 4772
428 4300 4773 4 G 47 477 4773
429 4310 4774 4 G 47 477 4774
430 4320 478 3 G 47 478
431 4330 4781 4 G 47 478 4781
432 4340 4782 4 G 47 478 4782
433 4350 4789 4 G 47 478 4789
434 4360 479 3 G 47 479
435 4370 4791 4 G 47 479 4791
436 4380 4799 4 G 47 479 4799
437 4390 H 1 H
438 4400 49 2 H 49
439 4410 491 3 H 49 491
440 4420 4911 4 H 49 491 4911
441 4430 4912 4 H 49 491 4912
442 4440 492 3 H 49 492
443 4450 4921 4 H 49 492 4921
444 4460 4922 4 H 49 492 4922
445 4470 4923 4 H 49 492 4923
446 4480 493 3 H 49 493
447 4490 4930 4 H 49 493 4930
448 4500 50 2 H 50
449 4510 501 3 H 50 501
450 4520 5011 4 H 50 501 5011
451 4530 5012 4 H 50 501 5012
452 4540 502 3 H 50 502
453 4550 5021 4 H 50 502 5021
454 4560 5022 4 H 50 502 5022
455 4570 51 2 H 51
456 4580 511 3 H 51 511
457 4590 5110 4 H 51 511 5110
458 4600 512 3 H 51 512
459 4610 5120 4 H 51 512 5120
460 4620 52 2 H 52
461 4630 521 3 H 52 521
462 4640 5210 4 H 52 521 5210
463 4650 522 3 H 52 522
464 4660 5221 4 H 52 522 5221
465 4670 5222 4 H 52 522 5222
466 4680 5223 4 H 52 522 5223
467 4690 5224 4 H 52 522 5224
468 4700 5229 4 H 52 522 5229
469 4710 53 2 H 53
470 4720 531 3 H 53 531
471 4730 5310 4 H 53 531 5310
472 4740 532 3 H 53 532
473 4750 5320 4 H 53 532 5320
474 4760 I 1 I
475 4770 55 2 I 55
476 4780 551 3 I 55 551
477 4790 5510 4 I 55 551 5510
478 4800 552 3 I 55 552
479 4810 5520 4 I 55 552 5520
480 4820 559 3 I 55 559
481 4830 5590 4 I 55 559 5590
482 4840 56 2 I 56
483 4850 561 3 I 56 561
484 4860 5610 4 I 56 561 5610
485 4870 562 3 I 56 562
486 4880 5621 4 I 56 562 5621
487 4890 5629 4 I 56 562 5629
488 4930 563 3 I 56 563
489 4940 5630 4 I 56 563 5630
490 4950 J 1 J
491 4960 58 2 J 58
492 4970 581 3 J 58 581
493 4980 5811 4 J 58 581 5811
494 4990 5812 4 J 58 581 5812
495 5000 5813 4 J 58 581 5813
496 5010 5819 4 J 58 581 5819
497 5020 582 3 J 58 582
498 5030 5820 4 J 58 582 5820
499 5040 59 2 J 59
500 5050 591 3 J 59 591
501 5060 5911 4 J 59 591 5911
502 5070 5912 4 J 59 591 5912
503 5080 5913 4 J 59 591 5913
504 5090 5914 4 J 59 591 5914
505 5100 592 3 J 59 592
506 5110 5920 4 J 59 592 5920
507 5120 60 2 J 60
508 5130 601 3 J 60 601
509 5140 6010 4 J 60 601 6010
510 5150 602 3 J 60 602
511 5160 6020 4 J 60 602 6020
512 5180 61 2 J 61
513 5190 611 3 J 61 611
514 5200 6110 4 J 61 611 6110
515 5210 612 3 J 61 612
516 5220 6120 4 J 61 612 6120
517 5230 613 3 J 61 613
518 5240 6130 4 J 61 613 6130
519 5250 619 3 J 61 619
520 5260 6190 4 J 61 619 6190
521 5270 62 2 J 62
522 5280 620 3 J 62 620
523 5290 6201 4 J 62 620 6201
524 5300 6202 4 J 62 620 6202
525 5310 6209 4 J 62 620 6209
526 5320 63 2 J 63
527 5330 631 3 J 63 631
528 5340 6311 4 J 63 631 6311
529 5350 6312 4 J 63 631 6312
530 5360 639 3 J 63 639
531 5370 6391 4 J 63 639 6391
532 5380 6399 4 J 63 639 6399
533 5390 K 1 K
534 5400 64 2 K 64
535 5410 641 3 K 64 641
536 5420 6411 4 K 64 641 6411
537 5430 6419 4 K 64 641 6419
538 5440 642 3 K 64 642
539 5450 6420 4 K 64 642 6420
540 5460 643 3 K 64 643
541 5470 6430 4 K 64 643 6430
542 5480 649 3 K 64 649
543 5490 6491 4 K 64 649 6491
544 5500 6492 4 K 64 649 6492
545 5510 6499 4 K 64 649 6499
546 5520 65 2 K 65
547 5530 651 3 K 65 651
548 5540 6511 4 K 65 651 6511
549 5550 6512 4 K 65 651 6512
550 5560 652 3 K 65 652
551 5570 6520 4 K 65 652 6520
552 5580 653 3 K 65 653
553 5590 6530 4 K 65 653 6530
554 5600 66 2 K 66
555 5610 661 3 K 66 661
556 5620 6611 4 K 66 661 6611
557 5630 6612 4 K 66 661 6612
558 5640 6619 4 K 66 661 6619
559 5650 662 3 K 66 662
560 5660 6621 4 K 66 662 6621
561 5670 6622 4 K 66 662 6622
562 5680 6629 4 K 66 662 6629
563 5690 663 3 K 66 663
564 5700 6630 4 K 66 663 6630
565 5710 L 1 L
566 5720 68 2 L 68
567 5730 681 3 L 68 681
568 5740 6810 4 L 68 681 6810
569 5750 682 3 L 68 682
570 5760 6820 4 L 68 682 6820
571 5770 M 1 M
572 5780 69 2 M 69
573 5790 691 3 M 69 691
574 5800 6910 4 M 69 691 6910
575 5810 692 3 M 69 692
576 5820 6920 4 M 69 692 6920
577 5830 70 2 M 70
578 5840 701 3 M 70 701
579 5850 7010 4 M 70 701 7010
580 5860 702 3 M 70 702
581 5870 7020 4 M 70 702 7020
582 5880 71 2 M 71
583 5890 711 3 M 71 711
584 5900 7110 4 M 71 711 7110
585 5910 712 3 M 71 712
586 5920 7120 4 M 71 712 7120
587 5930 72 2 M 72
588 5940 721 3 M 72 721
589 5960 7210 4 M 72 721 7210
590 5970 722 3 M 72 722
591 5980 7220 4 M 72 722 7220
592 5990 73 2 M 73
593 6000 731 3 M 73 731
594 6010 7310 4 M 73 731 7310
595 6020 732 3 M 73 732
596 6030 7320 4 M 73 732 7320
597 6040 74 2 M 74
598 6050 741 3 M 74 741
599 6060 7410 4 M 74 741 7410
600 6070 742 3 M 74 742
601 6080 7420 4 M 74 742 7420
602 6090 749 3 M 74 749
603 6100 7490 4 M 74 749 7490
604 6110 75 2 M 75
605 6120 750 3 M 75 750
606 6130 7500 4 M 75 750 7500
607 6140 N 1 N
608 6150 77 2 N 77
609 6160 771 3 N 77 771
610 6170 7710 4 N 77 771 7710
611 6180 772 3 N 77 772
612 6190 7721 4 N 77 772 7721
613 6200 7722 4 N 77 772 7722
614 6210 7729 4 N 77 772 7729
615 6220 773 3 N 77 773
616 6230 7730 4 N 77 773 7730
617 6240 774 3 N 77 774
618 6250 7740 4 N 77 774 7740
619 6260 78 2 N 78
620 6270 781 3 N 78 781
621 6280 7810 4 N 78 781 7810
622 6290 782 3 N 78 782
623 6300 7820 4 N 78 782 7820
624 6310 783 3 N 78 783
625 6320 7830 4 N 78 783 7830
626 6330 79 2 N 79
627 6340 791 3 N 79 791
628 6350 7911 4 N 79 791 7911
629 6360 7912 4 N 79 791 7912
630 6370 799 3 N 79 799
631 6380 7990 4 N 79 799 7990
632 6390 80 2 N 80
633 6400 801 3 N 80 801
634 6410 8010 4 N 80 801 8010
635 6420 802 3 N 80 802
636 6430 8020 4 N 80 802 8020
637 6440 803 3 N 80 803
638 6450 8030 4 N 80 803 8030
639 6460 81 2 N 81
640 6470 811 3 N 81 811
641 6480 8110 4 N 81 811 8110
642 6490 812 3 N 81 812
643 6500 8121 4 N 81 812 8121
644 6510 8129 4 N 81 812 8129
645 6520 813 3 N 81 813
646 6530 8130 4 N 81 813 8130
647 6540 82 2 N 82
648 6550 821 3 N 82 821
649 6560 8211 4 N 82 821 8211
650 6570 8219 4 N 82 821 8219
651 6580 822 3 N 82 822
652 6590 8220 4 N 82 822 8220
653 6600 823 3 N 82 823
654 6610 8230 4 N 82 823 8230
655 6620 829 3 N 82 829
656 6630 8291 4 N 82 829 8291
657 6640 8292 4 N 82 829 8292
658 6650 8299 4 N 82 829 8299
659 6660 O 1 O
660 6670 84 2 O 84
661 6680 841 3 O 84 841
662 6690 8411 4 O 84 841 8411
663 6700 8412 4 O 84 841 8412
664 6710 8413 4 O 84 841 8413
665 6730 842 3 O 84 842
666 6740 8421 4 O 84 842 8421
667 6750 8422 4 O 84 842 8422
668 6760 8423 4 O 84 842 8423
669 6770 843 3 O 84 843
670 6780 8430 4 O 84 843 8430
671 6790 P 1 P
672 6800 85 2 P 85
673 6810 851 3 P 85 851
674 6820 8510 4 P 85 851 8510
675 6830 852 3 P 85 852
676 6840 8521 4 P 85 852 8521
677 6850 8522 4 P 85 852 8522
678 6860 853 3 P 85 853
679 6870 8530 4 P 85 853 8530
680 6880 854 3 P 85 854
681 6890 8541 4 P 85 854 8541
682 6900 8542 4 P 85 854 8542
683 6910 8549 4 P 85 854 8549
684 6920 855 3 P 85 855
685 6930 8550 4 P 85 855 8550
686 6940 Q 1 Q
687 6950 86 2 Q 86
688 6960 861 3 Q 86 861
689 6970 8610 4 Q 86 861 8610
690 6980 862 3 Q 86 862
691 6990 8620 4 Q 86 862 8620
692 7000 869 3 Q 86 869
693 7010 8690 4 Q 86 869 8690
694 7020 87 2 Q 87
695 7030 871 3 Q 87 871
696 7040 8710 4 Q 87 871 8710
697 7050 872 3 Q 87 872
698 7060 8720 4 Q 87 872 8720
699 7070 873 3 Q 87 873
700 7080 8730 4 Q 87 873 8730
701 7090 879 3 Q 87 879
702 7100 8790 4 Q 87 879 8790
703 7110 88 2 Q 88
704 7120 881 3 Q 88 881
705 7130 8810 4 Q 88 881 8810
706 7140 889 3 Q 88 889
707 7150 8890 4 Q 88 889 8890
708 7160 R 1 R
709 7170 90 2 R 90
710 7180 900 3 R 90 900
711 7190 9000 4 R 90 900 9000
712 7200 91 2 R 91
713 7210 910 3 R 91 910
714 7215 9101 4 R 91 910 9101
715 7220 9102 4 R 91 910 9102
716 7230 9103 4 R 91 910 9103
717 7240 92 2 R 92
718 7250 920 3 R 92 920
719 7260 9200 4 R 92 920 9200
720 7270 93 2 R 93
721 7280 931 3 R 93 931
722 7290 9311 4 R 93 931 9311
723 7300 9312 4 R 93 931 9312
724 7310 9319 4 R 93 931 9319
725 7320 932 3 R 93 932
726 7330 9321 4 R 93 932 9321
727 7340 9329 4 R 93 932 9329
728 7350 S 1 S
729 7360 94 2 S 94
730 7370 941 3 S 94 941
731 7380 9411 4 S 94 941 9411
732 7390 9412 4 S 94 941 9412
733 7400 942 3 S 94 942
734 7410 9420 4 S 94 942 9420
735 7420 949 3 S 94 949
736 7430 9491 4 S 94 949 9491
737 7440 9492 4 S 94 949 9492
738 7450 9499 4 S 94 949 9499
739 7460 95 2 S 95
740 7470 951 3 S 95 951
741 7480 9511 4 S 95 951 9511
742 7490 9512 4 S 95 951 9512
743 7500 952 3 S 95 952
744 7510 9521 4 S 95 952 9521
745 7520 9522 4 S 95 952 9522
746 7530 9523 4 S 95 952 9523
747 7540 9524 4 S 95 952 9524
748 7550 9529 4 S 95 952 9529
749 7560 96 2 S 96
750 7570 960 3 S 96 960
751 7580 9601 4 S 96 960 9601
752 7590 9602 4 S 96 960 9602
753 7600 9603 4 S 96 960 9603
754 7610 9609 4 S 96 960 9609
755 7620 T 1 T
756 7630 97 2 T 97
757 7640 970 3 T 97 970
758 7650 9700 4 T 97 970 9700
759 7660 98 2 T 98
760 7670 981 3 T 98 981
761 7680 9810 4 T 98 981 9810
762 7690 982 3 T 98 982
763 7700 9820 4 T 98 982 9820
764 7710 U 1 U
765 7720 99 2 U 99
766 7730 990 3 U 99 990
767 7740 9900 4 U 99 990 9900

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,112 @@
-- UP Migration
-- Этот SQL-код будет выполнен вашим скриптом run-migrations.js
BEGIN;
-- 1. Создаем таблицу для названий уровней ISIC
CREATE TABLE IF NOT EXISTS isic_rev4_level_names (
code_level INTEGER PRIMARY KEY,
level_name_en TEXT
);
-- 2. Создаем основную таблицу для кодов ISIC
CREATE TABLE IF NOT EXISTS isic_rev4_codes (
sort_order INTEGER,
code VARCHAR(10) PRIMARY KEY,
description TEXT,
explanatory_note_inclusion TEXT,
explanatory_note_exclusion TEXT,
code_level INTEGER,
level1 VARCHAR(10),
level2 VARCHAR(10),
level3 VARCHAR(10),
level4 VARCHAR(10),
level5 VARCHAR(10),
level6 VARCHAR(10),
CONSTRAINT fk_code_level FOREIGN KEY (code_level) REFERENCES isic_rev4_level_names (code_level) -- Добавляем внешний ключ
);
-- 3. Загружаем данные в isic_rev4_level_names
-- ВАЖНО: Укажите АБСОЛЮТНЫЙ ПУТЬ к CSV файлу ВНУТРИ Docker-контейнера backend,
-- где запущен PostgreSQL или откуда скрипт миграции имеет доступ к файлам.
-- Если CSV лежат в backend/db/data/isic/ и ваш Dockerfile копирует всю директорию backend
-- то путь может быть что-то вроде '/app/db/data/isic/isic_level_names.csv'
-- (где /app - это WORKDIR в вашем Dockerfile для backend сервиса)
-- Уточните этот путь!
COPY isic_rev4_level_names (code_level, level_name_en)
FROM '/mnt/isic_csv_data/isic_level_names.csv' -- <--- ПУТЬ СООТВЕТСТВУЕТ ТОМУ, ЧТО В volumes
WITH (FORMAT CSV, HEADER TRUE, DELIMITER ',', QUOTE '"');
-- 4. Создаем временные таблицы для импорта основных данных ISIC
CREATE TEMP TABLE tmp_isic_titles (
sort_order_tmp INTEGER,
code_tmp VARCHAR(10),
description_tmp TEXT,
inclusion_tmp TEXT,
exclusion_tmp TEXT
) ON COMMIT DROP; -- Временная таблица удалится после коммита
CREATE TEMP TABLE tmp_isic_structure (
sort_order_tmp INTEGER,
code_tmp VARCHAR(10),
code_level_tmp INTEGER,
level1_tmp VARCHAR(10),
level2_tmp VARCHAR(10),
level3_tmp VARCHAR(10),
level4_tmp VARCHAR(10),
level5_tmp VARCHAR(10),
level6_tmp VARCHAR(10)
) ON COMMIT DROP; -- Временная таблица удалится после коммита
-- 5. Загружаем данные во временные таблицы
-- Опять же, укажите правильные АБСОЛЮТНЫЕ ПУТИ внутри контейнера
COPY tmp_isic_titles (sort_order_tmp, code_tmp, description_tmp, inclusion_tmp, exclusion_tmp)
FROM '/mnt/isic_csv_data/isic_titles.csv' -- <--- ПУТЬ СООТВЕТСТВУЕТ ТОМУ, ЧТО В volumes
WITH (FORMAT CSV, HEADER TRUE, DELIMITER ',', QUOTE '"');
COPY tmp_isic_structure (sort_order_tmp, code_tmp, code_level_tmp, level1_tmp, level2_tmp, level3_tmp, level4_tmp, level5_tmp, level6_tmp)
FROM '/mnt/isic_csv_data/isic_structure.csv' -- <--- ПУТЬ СООТВЕТСТВУЕТ ТОМУ, ЧТО В volumes
WITH (FORMAT CSV, HEADER TRUE, DELIMITER ',', QUOTE '"');
-- 6. Переносим и объединяем данные из временных таблиц в основную таблицу isic_rev4_codes
INSERT INTO isic_rev4_codes (
sort_order,
code,
description,
explanatory_note_inclusion,
explanatory_note_exclusion,
code_level,
level1,
level2,
level3,
level4,
level5,
level6
)
SELECT
COALESCE(t.sort_order_tmp, s.sort_order_tmp),
s.code_tmp,
t.description_tmp,
t.inclusion_tmp,
t.exclusion_tmp,
s.code_level_tmp,
s.level1_tmp,
s.level2_tmp,
s.level3_tmp,
s.level4_tmp,
s.level5_tmp,
s.level6_tmp
FROM
tmp_isic_structure s
LEFT JOIN
tmp_isic_titles t ON s.code_tmp = t.code_tmp;
COMMIT;
-- DOWN Migration
-- Этот SQL-код НЕ будет выполнен вашим текущим скриптом run-migrations.js для отката,
-- но его полезно иметь для ручного отката или если вы доработаете скрипт.
-- BEGIN;
-- DROP TABLE IF EXISTS isic_rev4_codes;
-- DROP TABLE IF EXISTS isic_rev4_level_names;
-- COMMIT;

101
backend/routes/geocoding.js Normal file
View File

@@ -0,0 +1,101 @@
// backend/routes/geocoding.js
const express = require('express');
const router = express.Router();
const axios = require('axios'); // Убедитесь, что axios установлен на бэкенде
const logger = require('../utils/logger'); // Если используете логгер
/**
* @swagger
* tags:
* name: Geocoding
* description: Прокси для сервисов геокодирования (например, Nominatim)
*/
/**
* @swagger
* /api/geocoding/nominatim-search:
* get:
* summary: Проксирует запрос к Nominatim API (search)
* tags: [Geocoding]
* description: Перенаправляет GET запрос со всеми его query параметрами к https://nominatim.openstreetmap.org/search. Это необходимо для обхода CORS ограничений в браузере.
* parameters:
* - in: query
* name: q
* schema:
* type: string
* required: true
* description: Строка адреса для поиска.
* - in: query
* name: format
* schema:
* type: string
* required: true
* description: Формат ответа (например, jsonv2).
* - in: query
* name: addressdetails
* schema:
* type: integer
* enum: [0, 1]
* description: Включить детализированный адрес (0 или 1).
* - in: query
* name: limit
* schema:
* type: integer
* description: Максимальное количество возвращаемых результатов.
* # Можно добавить сюда и другие параметры Nominatim API по мере необходимости
* responses:
* 200:
* description: Успешный ответ от Nominatim.
* content:
* application/json:
* schema:
* type: object # Или array, в зависимости от ответа Nominatim
* 500:
* description: Ошибка при запросе к Nominatim или внутренняя ошибка сервера.
*/
router.get('/nominatim-search', async (req, res) => {
try {
// Формируем URL для Nominatim, используя все query параметры из исходного запроса
const queryParams = new URLSearchParams(req.query);
const nominatimUrl = `https://nominatim.openstreetmap.org/search?${queryParams.toString()}`;
if (logger && typeof logger.info === 'function') {
logger.info(`[Geocoding] Proxying request to Nominatim: ${nominatimUrl}`);
} else {
console.log(`[Geocoding] Proxying request to Nominatim: ${nominatimUrl}`);
}
const nominatimResponse = await axios.get(nominatimUrl);
res.json(nominatimResponse.data);
} catch (error) {
let errorMessage = error.message;
let errorStatus = 500;
let errorDetails = null;
if (error.response) {
// Ошибка пришла от Nominatim (или сети)
errorMessage = error.response.data?.message || error.response.statusText || 'Error fetching data from Nominatim';
errorStatus = error.response.status || 500;
errorDetails = error.response.data;
} else if (error.request) {
// Запрос был сделан, но ответ не получен
errorMessage = 'No response received from Nominatim';
}
// Иначе это ошибка настройки axios или другая внутренняя ошибка
if (logger && typeof logger.error === 'function') {
logger.error(`[Geocoding] Error proxying to Nominatim: ${errorMessage}`, { status: errorStatus, details: errorDetails, query: req.query });
} else {
console.error(`[Geocoding] Error proxying to Nominatim: ${errorMessage}`, { status: errorStatus, details: errorDetails, query: req.query });
}
res.status(errorStatus).json({
message: 'Error processing geocoding request',
details: errorDetails || errorMessage
});
}
});
module.exports = router;

297
backend/routes/isic.js Normal file
View File

@@ -0,0 +1,297 @@
const express = require('express');
const router = express.Router();
const { pool } = require('../db'); // Убедитесь, что путь к вашему db-коннектору правильный
const logger = require('../utils/logger'); // Если используете логгер
/**
* @swagger
* tags:
* name: ISIC
* description: API для кодов Международной стандартной отраслевой классификации (ISIC)
*/
/**
* @swagger
* /api/isic/codes:
* get:
* summary: Получить список кодов ISIC с фильтрацией и пагинацией
* tags: [ISIC]
* parameters:
* - in: query
* name: level
* schema:
* type: integer
* description: Фильтр по уровню кода (1-4, иногда 5 или 6)
* - in: query
* name: parent_code
* schema:
* type: string
* description: Фильтр по родительскому коду (для получения дочерних кодов)
* - in: query
* name: search
* schema:
* type: string
* description: Поисковый запрос по коду или описанию
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: Номер страницы для пагинации
* - in: query
* name: limit
* schema:
* type: integer
* default: 25
* description: Количество элементов на странице
* responses:
* 200:
* description: Список кодов ISIC
* content:
* application/json:
* schema:
* type: object
* properties:
* totalItems:
* type: integer
* codes:
* type: array
* items:
* $ref: '#/components/schemas/IsicCode'
* totalPages:
* type: integer
* currentPage:
* type: integer
* 404:
* description: Родительский код не найден
* 500:
* description: Ошибка сервера
*/
router.get('/codes', async (req, res) => {
const { level, parent_code, search } = req.query;
const page = parseInt(req.query.page, 10) || 1;
const limit = parseInt(req.query.limit, 10) || 25;
const offset = (page - 1) * limit;
const baseQuerySelect = `
SELECT c.code, c.description, c.code_level, c.explanatory_note_inclusion, c.explanatory_note_exclusion,
l.level_name_en,
c.level1, c.level2, c.level3, c.level4, c.level5, c.level6
FROM isic_rev4_codes c
LEFT JOIN isic_rev4_level_names l ON c.code_level = l.code_level
`;
// Для подсчета JOIN с level_names не нужен, если только по level_name_en не будет фильтрации (пока нет)
const baseQueryCount = `
SELECT COUNT(*) AS total
FROM isic_rev4_codes c
`;
const conditions = [];
const queryParams = []; // Параметры для основного запроса (включая limit/offset)
const countQueryParams = []; // Параметры только для WHERE части (для count запроса)
if (level) {
const levelParam = parseInt(level, 10);
conditions.push(`c.code_level = $${queryParams.push(levelParam)}`);
countQueryParams.push(levelParam);
}
if (parent_code) {
try {
const parentResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]);
if (parentResult.rows.length > 0) {
const parentLevel = parentResult.rows[0].code_level;
if (parentLevel >= 1 && parentLevel < 6) {
conditions.push(`c.level${parentLevel} = $${queryParams.push(parent_code)}`);
countQueryParams.push(parent_code);
const childLevel = parentLevel + 1;
conditions.push(`c.code_level = $${queryParams.push(childLevel)}`);
countQueryParams.push(childLevel);
} else {
// Родительский код на максимальном уровне, нет дочерних.
// Возвращаем пустой результат сразу.
return res.json({ totalItems: 0, codes: [], totalPages: 0, currentPage: page });
}
} else {
// parent_code не найден
logger.warn(`Parent code not found: ${parent_code}`);
return res.status(404).json({ error: 'Parent code not found', totalItems: 0, codes: [], totalPages: 0, currentPage: page });
}
} catch (dbError) {
logger.error('Error fetching parent_code level:', dbError);
return res.status(500).json({ error: 'Internal server error while fetching parent data' });
}
}
if (search) {
const searchPattern = `%${search}%`;
// Используем один параметр для обоих ILIKE, чтобы $N был корректным в countQueryParams
conditions.push(`(c.code ILIKE $${queryParams.push(searchPattern)} OR c.description ILIKE $${queryParams.length})`); // $N для второго ILIKE будет тем же, что и для первого
countQueryParams.push(searchPattern);
}
let whereClause = '';
if (conditions.length > 0) {
// Переиндексируем плейсхолдеры для whereClause, т.к. queryParams и countQueryParams теперь разные
// Это более сложный момент, проще собирать whereClause с $1, $2 и т.д. и передавать countQueryParams
// Для простоты сейчас оставим как было, но это означает, что queryParams используется для генерации whereClause,
// а потом из него берутся countQueryParams.
// Корректный способ: перестроить whereClause для count с правильными индексами
let countWhereClauseConditions = [];
let currentCountParamIndex = 1;
if (level) {
countWhereClauseConditions.push(`c.code_level = $${currentCountParamIndex++}`);
}
if (parent_code) {
// Предполагаем, что parent_code уже добавлен в countQueryParams
const parentLevelResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]); // Нужно будет передать parent_code в countQueryParams
if (parentLevelResult.rows.length > 0) {
const parentLevel = parentLevelResult.rows[0].code_level;
if (parentLevel >=1 && parentLevel < 6) {
countWhereClauseConditions.push(`c.level${parentLevel} = $${currentCountParamIndex++}`);
countWhereClauseConditions.push(`c.code_level = $${currentCountParamIndex++}`);
}
}
}
if (search) {
countWhereClauseConditions.push(`(c.code ILIKE $${currentCountParamIndex} OR c.description ILIKE $${currentCountParamIndex})`);
currentCountParamIndex++;
}
whereClause = countWhereClauseConditions.length > 0 ? ' WHERE ' + countWhereClauseConditions.join(' AND ') : '';
}
// Запрос для получения данных с пагинацией
const dataQueryPlaceholders = queryParams.map((_, i) => `$${i + 1}`).join(', '); // Это неверно для WHERE
// Формируем finalQuery с плейсхолдерами, соответствующими queryParams
let finalQueryWhereClause = '';
if (conditions.length > 0) {
let currentQueryParamIndex = 1;
const queryWhereConditions = [];
if (level) queryWhereConditions.push(`c.code_level = $${currentQueryParamIndex++}`);
if (parent_code) {
const parentLevelResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]); // Это дублирование, лучше получить parentLevel один раз
if (parentLevelResult.rows.length > 0) {
const parentLevel = parentLevelResult.rows[0].code_level;
if (parentLevel >=1 && parentLevel < 6) {
queryWhereConditions.push(`c.level${parentLevel} = $${currentQueryParamIndex++}`);
queryWhereConditions.push(`c.code_level = $${currentQueryParamIndex++}`);
}
}
}
if (search) queryWhereConditions.push(`(c.code ILIKE $${currentQueryParamIndex} OR c.description ILIKE $${currentQueryParamIndex})`); // searchPattern идет одним параметром
finalQueryWhereClause = queryWhereConditions.length > 0 ? ' WHERE ' + queryWhereConditions.join(' AND ') : '';
}
const finalQuery = `${baseQuerySelect} ${finalQueryWhereClause} ORDER BY c.sort_order, c.code LIMIT $${queryParams.push(limit)} OFFSET $${queryParams.push(offset)}`;
const finalCountQuery = `${baseQueryCount} ${whereClause}`;
try {
logger.debug('Executing count query:', finalCountQuery, 'Params:', countQueryParams);
const totalItemsResult = await pool.query(finalCountQuery, countQueryParams);
const totalItems = parseInt(totalItemsResult.rows[0].total, 10);
// Параметры для основного запроса - это все, что в queryParams (включая limit и offset)
logger.debug('Executing data query:', finalQuery, 'Params:', queryParams);
const result = await pool.query(finalQuery, queryParams);
res.json({
totalItems,
codes: result.rows,
totalPages: Math.ceil(totalItems / limit),
currentPage: page,
});
} catch (error) {
logger.error('Error fetching ISIC codes:', error);
if (error.query) logger.error('Failed Query:', error.query);
if (error.parameters) logger.error('Failed Params:', error.parameters);
res.status(500).json({ error: 'Internal server error' });
}
});
/**
* @swagger
* /api/isic/tree:
* get:
* summary: Получить иерархическое дерево кодов ISIC (или его часть)
* tags: [ISIC]
* parameters:
* - in: query
* name: root_code
* schema:
* type: string
* description: Код ISIC, с которого начинать построение дерева (например, 'A' или '01'). Если не указан, вернет все секции (уровень 1).
* - in: max_depth
* schema:
* type: integer
* default: 2
* description: Максимальная глубина дерева для загрузки.
* responses:
* 200:
* description: Дерево кодов ISIC
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/IsicTreeNode' # Определим эту схему позже
* 500:
* description: Ошибка сервера
*/
router.get('/tree', async (req, res) => {
const { root_code, max_depth = 2 } = req.query;
// Эта функция потребует рекурсивного запроса или сложного CTE (Common Table Expression) в SQL
// для построения дерева. Это более сложная задача.
// Для начала можно сделать упрощенную версию, которая возвращает один уровень вложенности.
try {
let items;
if (!root_code) { // Если нет root_code, возвращаем секции (уровень 1)
const result = await pool.query(
"SELECT code, description, code_level FROM isic_rev4_codes WHERE code_level = 1 ORDER BY sort_order, code"
);
items = result.rows.map(row => ({ ...row, children: [] })); // Добавляем пустой массив children
} else {
// Получаем сам root_code
const rootResult = await pool.query(
"SELECT code, description, code_level FROM isic_rev4_codes WHERE code = $1",
[root_code]
);
if (rootResult.rows.length === 0) {
return res.status(404).json({ error: 'Root code not found' });
}
const rootNode = { ...rootResult.rows[0], children: [] };
// Получаем прямых потомков (упрощенный пример для одного уровня вложенности)
let childrenQuery = '';
const childrenParams = [root_code];
const rootLevel = rootNode.code_level;
if (rootLevel === 1) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level1 = $1 AND code_level = 2 ORDER BY sort_order, code";
else if (rootLevel === 2) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level2 = $1 AND code_level = 3 ORDER BY sort_order, code";
else if (rootLevel === 3) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level3 = $1 AND code_level = 4 ORDER BY sort_order, code";
else if (rootLevel === 4) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level4 = $1 AND code_level = 5 ORDER BY sort_order, code";
else if (rootLevel === 5) childrenQuery = "SELECT code, description, code_level FROM isic_rev4_codes WHERE level5 = $1 AND code_level = 6 ORDER BY sort_order, code";
if (childrenQuery) {
const childrenResult = await pool.query(childrenQuery, childrenParams);
rootNode.children = childrenResult.rows.map(row => ({ ...row, children: [] }));
}
items = [rootNode];
}
res.json(items);
} catch (error)
{
logger.error('Error fetching ISIC tree:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;

View File

@@ -7,6 +7,7 @@ services:
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backend/db/data:/mnt/isic_csv_data
environment:
POSTGRES_DB: ${DB_NAME:-dapp_db}
POSTGRES_USER: ${DB_USER:-dapp_user}

View File

@@ -14,7 +14,6 @@
<p>Для доступа к настройкам необходимо <button @click="goToHomeAndShowSidebar">войти</button>.</p>
</div>
<div v-else class="settings-navigation-buttons">
<h3>Разделы настроек:</h3>
<div class="buttons-grid">
<router-link :to="{ name: 'settings-ai' }" class="btn btn-secondary">ИИ</router-link>
<router-link :to="{ name: 'settings-blockchain' }" class="btn btn-secondary">Блокчейн</router-link>

View File

@@ -1,73 +1,641 @@
<template>
<div class="blockchain-settings settings-panel">
<h2>Настройки Блокчейна</h2>
<!-- Панель Смарт-контракты -->
<!-- Панель Создать новое DLE (Digital Legal Entity) -->
<div class="sub-settings-panel">
<h3>Настройки смарт-контрактов</h3>
<h3>Создать новое DLE (Digital Legal Entity)</h3>
<div class="setting-form">
<p>Управление смарт-контрактами</p>
<p>Настройка и деплой нового DLE (Digital Legal Entity) с токеном управления и контрактом Governor.</p>
<div class="form-group">
<label class="form-label">Адрес основного контракта:</label>
<input type="text" v-model="settings.contractAddress" class="form-control">
<label class="form-label" for="blockchainNetwork">Цепочка блокчейна для деплоя:</label>
<select id="blockchainNetwork" v-model="dleDeploymentSettings.blockchainNetwork" class="form-control">
<option value="polygon">Polygon (Matic)</option>
<option value="ethereum_mainnet">Ethereum Mainnet</option>
<option value="sepolia">Sepolia (Testnet)</option>
<option value="goerli">Goerli (Testnet)</option>
<!-- TODO: Добавить другие сети по мере необходимости -->
</select>
</div>
<button class="btn btn-primary" @click="saveSettings('smartContract')">Сохранить</button>
<div class="form-group">
<label class="form-label" for="dleName">Имя DLE (Digital Legal Entity) (и токена):</label>
<input type="text" id="dleName" v-model="dleDeploymentSettings.name" class="form-control" placeholder="Например, My DLE">
</div>
<div class="form-group">
<label class="form-label" for="dleSymbol">Символ токена управления (GT):</label>
<input type="text" id="dleSymbol" v-model="dleDeploymentSettings.symbol" class="form-control" placeholder="Например, MDGT (3-5 символов)">
</div>
<h4>Местонахождение</h4>
<div class="address-grid">
<div class="form-group address-index">
<label class="form-label" for="locIndex">Индекс:</label>
<div class="input-group">
<input type="text" id="locIndex" v-model="dleDeploymentSettings.locationIndex" @input="checkIndexInput" class="form-control">
<button class="btn btn-outline-secondary btn-sm" type="button" @click="fetchAddressByZipcode" :disabled="isFetchingByZipcode || !dleDeploymentSettings.locationIndex">
<i class="fas fa-search"></i> {{ isFetchingByZipcode ? 'Поиск...' : 'Найти по индексу' }}
</button>
</div>
</div>
<!-- Панель Кворум -->
<div class="sub-settings-panel">
<h3>Настройки кворума</h3>
<div class="setting-form">
<p>Настройки кворума для блокчейн-операций</p>
<div class="form-group">
<label class="form-label">Минимальный кворум (%):</label>
<input type="number" v-model="settings.quorumPercent" min="0" max="100" class="form-control">
<template v-if="addressFieldsVisible">
<div class="form-group address-country">
<label class="form-label" for="locCountry">Страна:</label>
<input type="text" id="locCountry" v-model="dleDeploymentSettings.locationCountry" class="form-control">
</div>
<button class="btn btn-primary" @click="saveSettings('quorum')">Сохранить</button>
<div class="form-group address-city">
<label class="form-label" for="locCity">Населенный пункт:</label>
<input type="text" id="locCity" v-model="dleDeploymentSettings.locationCity" class="form-control">
</div>
<div class="form-group address-street">
<label class="form-label" for="locStreet">Улица:</label>
<input type="text" id="locStreet" v-model="dleDeploymentSettings.locationStreet" class="form-control">
</div>
<div class="form-group address-house">
<label class="form-label" for="locHouse">Дом:</label>
<input type="text" id="locHouse" v-model="dleDeploymentSettings.locationHouse" class="form-control">
</div>
<div class="form-group address-office">
<label class="form-label" for="locOffice">Офис/Кв.:</label>
<input type="text" id="locOffice" v-model="dleDeploymentSettings.locationOffice" class="form-control">
</div>
</template>
</div>
<div class="address-verification-section" v-if="addressFieldsVisible">
<button @click="verifyAddress" :disabled="isAddressVerifying" class="btn btn-info btn-sm">
<i class="fas fa-search-location"></i> {{ isAddressVerifying ? 'Проверка адреса...' : 'Проверить адрес' }}
</button>
<div v-if="addressVerificationResult" class="verification-status alert mt-2">
<p v-if="addressVerificationResult === 'verified_exact'" class="alert-success"> Адрес подтвержден (точный).</p>
<p v-if="addressVerificationResult === 'verified_street'" class="alert-success"> Адрес подтвержден (улица найдена).</p>
<p v-if="addressVerificationResult === 'verified_city'" class="alert-success"> Адрес подтвержден (город найден).</p>
<p v-if="addressVerificationResult === 'verified_ambiguous'" class="alert-warning"> Адрес найден, но требует уточнения.</p>
<p v-if="addressVerificationResult === 'not_found'" class="alert-danger"> Адрес не найден.</p>
<p v-if="addressVerificationResult === 'ambiguous'" class="alert-warning"> Найденный адрес не полностью совпадает с введенным. Уточните запрос.</p>
<p v-if="addressVerificationResult === 'error'" class="alert-danger"> Ошибка при проверке адреса.</p>
<details v-if="verifiedAddressDetails" class="mt-2">
<summary>Детали от Nominatim</summary>
<pre class="code-block">{{ JSON.stringify(verifiedAddressDetails, null, 2) }}</pre>
</details>
</div>
</div>
<!-- Панель RWA -->
<div class="sub-settings-panel">
<h3>Настройки Real World Assets (RWA)</h3>
<div class="setting-form">
<p>Конфигурация для работы с реальными активами</p>
<div v-if="dleDeploymentSettings.selectedIsicCodes && dleDeploymentSettings.selectedIsicCodes.length > 0" class="isic-codes-list mt-3">
<h5>Добавленные коды деятельности:</h5>
<ul>
<li v-for="(isic, index) in dleDeploymentSettings.selectedIsicCodes" :key="index" class="d-flex justify-content-between align-items-center mb-1">
<span>{{ isic.text }} ({{ isic.code }})</span>
<button @click="removeIsicCode(index)" class="btn btn-danger btn-xs">Удалить</button>
</li>
</ul>
</div>
<div class="form-group">
<label class="form-label">
<input type="checkbox" v-model="settings.rwaEnabled">
Включить поддержку RWA
</label>
<label class="form-label" for="isicSection">Выберите код деятельности:</label>
<select id="isicSection" v-model="selectedSection" class="form-control" :disabled="isLoadingSections">
<option value="">-- {{ isLoadingSections ? 'Загрузка секций...' : 'Выберите секцию' }} --</option>
<option v-for="option in sectionOptions" :key="option.value" :value="option.value">
{{ option.text }}
</option>
</select>
</div>
<button class="btn btn-primary" @click="saveSettings('rwa')">Сохранить</button>
<div class="form-group" v-if="selectedSection">
<label class="form-label" for="isicDivision">Раздел:</label>
<select id="isicDivision" v-model="selectedDivision" class="form-control" :disabled="isLoadingDivisions">
<option value="">-- {{ isLoadingDivisions ? 'Загрузка разделов...' : 'Выберите раздел' }} --</option>
<option v-for="option in divisionOptions" :key="option.value" :value="option.value">
{{ option.text }}
</option>
</select>
</div>
<div class="form-group" v-if="selectedDivision">
<label class="form-label" for="isicGroup">Группа:</label>
<select id="isicGroup" v-model="selectedGroup" class="form-control" :disabled="isLoadingGroups">
<option value="">-- {{ isLoadingGroups ? 'Загрузка групп...' : 'Выберите группу' }} --</option>
<option v-for="option in groupOptions" :key="option.value" :value="option.value">
{{ option.text }}
</option>
</select>
</div>
<div class="form-group" v-if="selectedGroup">
<label class="form-label" for="isicClass">Класс:</label>
<select id="isicClass" v-model="selectedClass" class="form-control" :disabled="isLoadingClasses">
<option value="">-- {{ isLoadingClasses ? 'Загрузка классов...' : 'Выберите класс' }} --</option>
<option v-for="option in classOptions" :key="option.value" :value="option.value">
{{ option.text }}
</option>
</select>
</div>
<div v-if="currentSelectedIsicText" class="current-isic-selection">
<p><strong>Выбранный код:</strong> {{ currentSelectedIsicText }}</p>
<button @click="addIsicCode" class="btn btn-success btn-sm" :disabled="!currentSelectedIsicCode">Добавить код деятельности</button>
</div>
<h4>Первоначальное распределение токенов управления</h4>
<div v-for="(partner, index) in dleDeploymentSettings.partners" :key="index" class="partner-entry">
<div class="form-group">
<label class="form-label">Адрес партнера {{ index + 1 }}:</label>
<input type="text" v-model="partner.address" class="form-control" placeholder="0x...">
</div>
<div class="form-group">
<label class="form-label">Сумма GT для партнера {{ index + 1 }}:</label>
<input type="number" v-model="partner.amount" min="1" class="form-control">
</div>
<button class="btn btn-danger btn-sm" @click="removePartner(index)">Удалить партнера</button>
</div>
<button class="btn btn-secondary" @click="addPartner">Добавить партнера</button>
<div class="form-group">
<label class="form-label">Общее количество выпускаемых GT: {{ totalInitialSupply }}</label>
</div>
<h4>Настройки Governor</h4>
<div class="form-group">
<label class="form-label" for="proposalThreshold">Порог для создания предложений (кол-во GT):</label>
<input type="number" id="proposalThreshold" v-model="dleDeploymentSettings.proposalThreshold" min="0" class="form-control">
</div>
<div class="form-group">
<label class="form-label" for="quorumPercentGovernor">Кворум (% от общего числа голосов):</label>
<input type="number" id="quorumPercentGovernor" v-model="dleDeploymentSettings.quorumPercent" min="1" max="100" class="form-control">
</div>
<div class="form-group">
<label class="form-label" for="votingDelay">Задержка перед голосованием (в днях):</label>
<input type="number" id="votingDelay" v-model="dleDeploymentSettings.votingDelayDays" min="0" class="form-control">
</div>
<div class="form-group">
<label class="form-label" for="votingPeriod">Период голосования (в днях):</label>
<input type="number" id="votingPeriod" v-model="dleDeploymentSettings.votingPeriodDays" min="1" class="form-control">
</div>
<h4>Настройки Timelock (если используется)</h4>
<div class="form-group">
<label class="form-label" for="timelockMinDelay">Минимальная задержка Timelock (в днях):</label>
<input type="number" id="timelockMinDelay" v-model="dleDeploymentSettings.timelockMinDelayDays" min="0" class="form-control">
</div>
<button class="btn btn-primary btn-lg" @click="deployDLE">Создать и задеплоить DLE (Digital Legal Entity)</button>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, onMounted } from 'vue';
import { reactive, onMounted, computed, ref, watch } from 'vue';
import axios from 'axios'; // Предполагаем, что axios доступен
// TODO: Импортировать API
const settings = reactive({
contractAddress: '',
quorumPercent: 51,
rwaEnabled: false
// contractAddress: '', // Удалено
// quorumPercent: 51, // Удалено
// rwaEnabled: false // Удалено
});
onMounted(() => {
loadBlockchainSettings();
const dleDeploymentSettings = reactive({
name: '',
symbol: '',
partners: [{ address: '', amount: 1 }], // Начинаем с одного партнера для примера
proposalThreshold: 0, // Голосов для создания предложения
quorumPercent: 4, // Процент для кворума Governor
votingDelayDays: 1, // в днях
votingPeriodDays: 7, // в днях
timelockMinDelayDays: 2, // в днях
blockchainNetwork: 'polygon', // Значение по умолчанию
locationIndex: '',
locationCountry: '',
locationCity: '',
locationStreet: '',
locationHouse: '',
locationOffice: '',
selectedIsicCodes: [], // <<< Для хранения массива выбранных кодов ISIC
});
// --- Состояние для загрузки и опций ISIC ---
const sectionOptions = ref([]);
const divisionOptions = ref([]);
const groupOptions = ref([]);
const classOptions = ref([]);
const isLoadingSections = ref(false);
const isLoadingDivisions = ref(false);
const isLoadingGroups = ref(false);
const isLoadingClasses = ref(false);
// --- Состояние для проверки адреса Nominatim ---
const isAddressVerifying = ref(false);
const addressVerificationResult = ref(null); // null, 'verified_exact', 'verified_street', 'verified_city', 'verified_ambiguous', 'not_found', 'ambiguous', 'error'
const verifiedAddressDetails = ref(null);
const isFetchingByZipcode = ref(false);
const addressFieldsVisible = ref(false);
// --- Состояние для выбранных значений на каждом уровне ISIC ---
const selectedSection = ref('');
const selectedDivision = ref('');
const selectedGroup = ref('');
const selectedClass = ref('');
// --- Для хранения текущего самого детализированного выбора ISIC до добавления в список ---
const currentSelectedIsicCode = ref('');
const currentSelectedIsicText = ref('');
// --- Отслеживание изменения страны ---
watch(() => dleDeploymentSettings.locationCountry, (newCountry, oldCountry) => {
if (newCountry !== oldCountry) {
console.log(`[BlockchainSettingsView] Страна изменена на: ${newCountry}. Очистка кодов деятельности.`);
selectedSection.value = ''; // Это вызовет каскадную очистку и сброс currentSelectedIsicCode
dleDeploymentSettings.selectedIsicCodes = []; // Очищаем также список уже добавленных кодов
fetchIsicCodes({ level: 1 }, sectionOptions, isLoadingSections);
}
});
// --- Функция для загрузки кодов ISIC из API ---
const fetchIsicCodes = async (params = {}, optionsRef, loadingRef) => {
if (!optionsRef || !loadingRef) {
console.error('[BlockchainSettingsView] fetchIsicCodes requires optionsRef and loadingRef');
return;
}
loadingRef.value = true;
optionsRef.value = []; // Очищаем перед загрузкой
try {
const queryParams = new URLSearchParams(params).toString();
console.debug(`[BlockchainSettingsView] Fetching ISIC codes with params: ${queryParams}`);
// Убедитесь, что базовый URL настроен правильно (например, через axios interceptors или .env)
const response = await axios.get(`/api/isic/codes?${queryParams}`);
if (response.data && Array.isArray(response.data.codes)) {
optionsRef.value = response.data.codes.map(code => ({
value: code.code,
// Отображаем код и описание для ясности
text: `${code.code} - ${code.description}`
}));
console.debug(`[BlockchainSettingsView] Loaded ISIC codes for level ${params.level || ('parent: '+params.parent_code)}, count:`, optionsRef.value.length);
} else {
console.error('[BlockchainSettingsView] Invalid response structure for ISIC codes:', response.data);
}
} catch (error) {
console.error('[BlockchainSettingsView] Error fetching ISIC codes:', error.response?.data || error.message);
// TODO: Показать пользователю уведомление об ошибке
} finally {
loadingRef.value = false;
}
};
// --- Функция для обновления информации о текущем полном выбранном коде ISIC ---
const updateCurrentIsicSelection = () => {
let code = '';
let text = '';
let optionsToSearch = [];
let valueToFind = '';
if (selectedClass.value) {
code = selectedClass.value;
optionsToSearch = classOptions.value;
valueToFind = selectedClass.value;
} else if (selectedGroup.value) {
code = selectedGroup.value;
optionsToSearch = groupOptions.value;
valueToFind = selectedGroup.value;
} else if (selectedDivision.value) {
code = selectedDivision.value;
optionsToSearch = divisionOptions.value;
valueToFind = selectedDivision.value;
} else if (selectedSection.value) {
code = selectedSection.value;
optionsToSearch = sectionOptions.value;
valueToFind = selectedSection.value;
}
if (code && optionsToSearch.length > 0 && valueToFind) {
const foundOption = optionsToSearch.find(opt => opt.value === valueToFind);
if (foundOption) {
text = foundOption.text;
}
}
currentSelectedIsicCode.value = code;
currentSelectedIsicText.value = text;
};
// --- Наблюдатели для каскадной загрузки и обновления текущего выбора ---
watch(selectedSection, (newVal) => {
selectedDivision.value = ''; divisionOptions.value = [];
selectedGroup.value = ''; groupOptions.value = [];
selectedClass.value = ''; classOptions.value = [];
if (newVal) {
fetchIsicCodes({ parent_code: newVal }, divisionOptions, isLoadingDivisions);
}
updateCurrentIsicSelection();
});
watch(selectedDivision, (newVal) => {
selectedGroup.value = ''; groupOptions.value = [];
selectedClass.value = ''; classOptions.value = [];
if (newVal) {
fetchIsicCodes({ parent_code: newVal }, groupOptions, isLoadingGroups);
}
updateCurrentIsicSelection();
});
watch(selectedGroup, (newVal) => {
selectedClass.value = ''; classOptions.value = [];
if (newVal) {
fetchIsicCodes({ parent_code: newVal }, classOptions, isLoadingClasses);
}
updateCurrentIsicSelection();
});
watch(selectedClass, () => {
updateCurrentIsicSelection();
});
// --- Начальная загрузка данных ---
onMounted(() => {
fetchIsicCodes({ level: 1 }, sectionOptions, isLoadingSections);
// TODO: Загрузить настройки блокчейна, если они есть
// loadBlockchainSettings(); // Эта функция пока не актуальна, так как settings пустой
});
const totalInitialSupply = computed(() => {
return dleDeploymentSettings.partners.reduce((sum, partner) => sum + (Number(partner.amount) || 0), 0);
});
const addPartner = () => {
dleDeploymentSettings.partners.push({ address: '', amount: 1 });
};
const removePartner = (index) => {
dleDeploymentSettings.partners.splice(index, 1);
};
const loadBlockchainSettings = async () => {
console.log('[BlockchainSettingsView] Загрузка настроек блокчейна...');
// TODO: API call
// TODO: API call - Больше нет общих настроек для загрузки в 'settings'.
// Возможно, потребуется загрузить dleDeploymentSettings, если они сохраняются.
};
const saveSettings = async (section) => {
console.log(`[BlockchainSettingsView] Сохранение настроек раздела: ${section}`);
// TODO: API call
// TODO: API call - Функция saveSettings, вероятно, больше не нужна в текущем виде,
// так как нет общих настроек для сохранения. Деплой DLE обрабатывается отдельно.
// Если настройки DLE (dleDeploymentSettings) нужно сохранять без деплоя, нужна другая логика.
};
const deployDLE = async () => {
console.log('[BlockchainSettingsView] Попытка деплоя DLE (Digital Legal Entity) с настройками:', JSON.parse(JSON.stringify(dleDeploymentSettings)));
console.log('[BlockchainSettingsView] Выбранные коды ISIC:', JSON.parse(JSON.stringify(dleDeploymentSettings.selectedIsicCodes)));
console.log('[BlockchainSettingsView] Общее начальное количество токенов:', totalInitialSupply.value);
const addressString = [
dleDeploymentSettings.locationIndex,
dleDeploymentSettings.locationCountry,
dleDeploymentSettings.locationCity,
dleDeploymentSettings.locationStreet,
dleDeploymentSettings.locationHouse,
dleDeploymentSettings.locationOffice
].filter(Boolean).join(', '); // Собираем строку адреса для alert/log
let finalIsicDisplay = 'Не выбраны';
if (dleDeploymentSettings.selectedIsicCodes && dleDeploymentSettings.selectedIsicCodes.length > 0) {
finalIsicDisplay = dleDeploymentSettings.selectedIsicCodes.map(c => c.code).join(', ');
}
alert(`Деплой DLE (Digital Legal Entity) инициирован (см. консоль). Имя: ${dleDeploymentSettings.name}, Символ: ${dleDeploymentSettings.symbol}, Сеть: ${dleDeploymentSettings.blockchainNetwork}, Коды деят: ${finalIsicDisplay}, Адрес: ${addressString || 'Не указан'}`);
// TODO: Вызвать API бэкенда для деплоя контрактов
// Передать dleDeploymentSettings.blockchainNetwork на бэкенд
// Передать выбранный dleDeploymentSettings.selectedIsicCodes и детали адреса (dleDeploymentSettings.location...) (возможно, для метаданных контракта или внесения в реестр)
// 1. Деплой ERC20Votes токена с параметрами:
// - name: dleDeploymentSettings.name
// - symbol: dleDeploymentSettings.symbol
// - initialSupply: totalInitialSupply.value (или как решит бэкенд по партнерам)
// - initialHolders: dleDeploymentSettings.partners (адреса и суммы)
// 2. Деплой TimelockController (если используется, получить его адрес)
// - minDelay: dleDeploymentSettings.timelockMinDelayDays * 24 * 60 * 60 (конвертация в секунды)
// - proposers: [адрес будущего Governor]
// - executors: [0x0000...0000] (любой может исполнить) или [адрес будущего Governor]
// 3. Деплой Governor контракта:
// - name: dleDeploymentSettings.name + " Governor" (или просто уникальное имя для Governor)
// - tokenAddress: адрес задеплоенного ERC20Votes
// - timelockAddress: адрес задеплоенного TimelockController (если используется)
// - quorumPercent: dleDeploymentSettings.quorumPercent
// - proposalThreshold: dleDeploymentSettings.proposalThreshold
// - votingPeriod: dleDeploymentSettings.votingPeriodDays * 24 * 60 * 60 (конвертация в секунды или блоки на бэкенде)
// - votingDelay: dleDeploymentSettings.votingDelayDays * 24 * 60 * 60 (конвертация в секунды или блоки на бэкенде)
// 4. (Если Timelock) Передать права администратора Timelock самому Timelock'у (или DLE)
// 5. (Опционально) Передать права на другие системные контракты Timelock'у
// TODO: Передать dleDeploymentSettings.selectedIsicCodes (массив объектов) на бэкенд
};
// --- Функция для поиска адреса по индексу через Nominatim ---
const fetchAddressByZipcode = async () => {
const zipcode = dleDeploymentSettings.locationIndex.trim();
if (!zipcode) {
return;
}
isFetchingByZipcode.value = true;
addressVerificationResult.value = null;
verifiedAddressDetails.value = null;
try {
const params = new URLSearchParams();
params.append('postalcode', zipcode);
params.append('format', 'jsonv2');
params.append('addressdetails', '1');
params.append('limit', '1');
const countryInput = dleDeploymentSettings.locationCountry.trim();
if (countryInput) {
if (countryInput.length === 2) {
params.append('countrycodes', countryInput.toUpperCase());
} else {
params.append('country', countryInput);
}
}
console.log(`[FetchByZipcode] Querying backend proxy for Nominatim with: ${params.toString()}`);
const response = await axios.get(`/api/geocoding/nominatim-search?${params.toString()}`);
if (response.data && response.data.length > 0) {
const bestMatch = response.data[0];
console.log('[FetchByZipcode] Nominatim result:', bestMatch);
if (bestMatch.address) {
if (bestMatch.address.country) {
dleDeploymentSettings.locationCountry = bestMatch.address.country;
}
if (bestMatch.address.city) {
dleDeploymentSettings.locationCity = bestMatch.address.city;
} else if (bestMatch.address.town) {
dleDeploymentSettings.locationCity = bestMatch.address.town;
} else if (bestMatch.address.village) {
dleDeploymentSettings.locationCity = bestMatch.address.village;
} else {
// Город не найден четко, можно оставить поле пустым или сообщить
}
// Можно также попробовать заполнить регион/область, если есть такое поле
// dleDeploymentSettings.locationState = bestMatch.address.state;
addressFieldsVisible.value = true;
} else {
addressFieldsVisible.value = false;
}
} else {
addressFieldsVisible.value = false;
}
} catch (error) {
console.error('[FetchByZipcode] Error fetching address by zipcode:', error.response?.data || error.message);
addressFieldsVisible.value = false;
} finally {
isFetchingByZipcode.value = false;
}
};
// Дополнительная функция для скрытия полей, если индекс очищен
const checkIndexInput = () => {
if (!dleDeploymentSettings.locationIndex.trim()) {
addressFieldsVisible.value = false;
// Опционально: также можно очистить dleDeploymentSettings.locationCountry, city и т.д.
// dleDeploymentSettings.locationCountry = '';
// dleDeploymentSettings.locationCity = '';
}
};
// --- Функция для сборки строки адреса для Nominatim (для кнопки "Проверить адрес") ---
const buildAddressQuery = () => {
const parts = [];
let streetHouse = '';
const street = dleDeploymentSettings.locationStreet.trim();
const house = dleDeploymentSettings.locationHouse.trim().toLowerCase(); // Приводим номер дома к нижнему регистру
if (street) {
streetHouse += street;
}
if (house) {
streetHouse += (streetHouse ? ' ' : '') + house;
}
if (streetHouse) {
parts.push(streetHouse);
}
const city = dleDeploymentSettings.locationCity.trim();
if (city) {
parts.push(city);
}
const country = dleDeploymentSettings.locationCountry.trim();
if (country) {
parts.push(country);
}
return parts.join(', ');
};
// --- Функция для проверки адреса через Nominatim ---
const verifyAddress = async () => {
const addressQuery = buildAddressQuery();
if (!addressQuery.trim()) {
addressVerificationResult.value = null;
verifiedAddressDetails.value = null;
return;
}
isAddressVerifying.value = true;
addressVerificationResult.value = null;
verifiedAddressDetails.value = null;
try {
const params = new URLSearchParams({
q: addressQuery,
format: 'jsonv2',
addressdetails: 1,
limit: 5,
// countrycodes параметр убран, т.к. страна теперь свободный текстовый ввод
// Nominatim попытается определить ее из 'q'
});
console.log(`[VerifyAddress] Querying backend proxy for Nominatim with: ${params.toString()}`);
// Запрос теперь идет на ваш бэкенд-прокси
const response = await axios.get(`/api/geocoding/nominatim-search?${params.toString()}`);
// Ответ от бэкенд-прокси должен иметь ту же структуру, что и прямой ответ от Nominatim
if (response.data && Array.isArray(response.data)) { // Проверяем, что это массив (как отвечает Nominatim)
if (response.data.length > 0) {
const bestMatch = response.data[0];
verifiedAddressDetails.value = bestMatch;
console.log('[VerifyAddress] Nominatim best match via proxy:', bestMatch);
let countryMatches = true;
if (dleDeploymentSettings.locationCountry && bestMatch.address.country_code) {
if (dleDeploymentSettings.locationCountry.trim().toUpperCase() !== bestMatch.address.country_code.toUpperCase()) {
if (dleDeploymentSettings.locationCountry.length === 2) countryMatches = false;
}
} else if (dleDeploymentSettings.locationCountry && !bestMatch.address.country_code) {
countryMatches = false;
}
if (countryMatches) {
if (bestMatch.address.house_number && bestMatch.address.road) {
addressVerificationResult.value = 'verified_exact';
} else if (bestMatch.address.road) {
addressVerificationResult.value = 'verified_street';
} else if (bestMatch.address.city || bestMatch.address.town || bestMatch.address.village) {
addressVerificationResult.value = 'verified_city';
} else {
addressVerificationResult.value = 'verified_ambiguous';
}
} else {
addressVerificationResult.value = 'ambiguous';
}
} else {
// Nominatim вернул пустой массив - адрес не найден
addressVerificationResult.value = 'not_found';
}
} else {
// Ответ от бэкенд-прокси не в ожидаемом формате (не массив)
console.error('[VerifyAddress] Invalid response structure from backend proxy:', response.data);
addressVerificationResult.value = 'error'; // или более специфичная ошибка
verifiedAddressDetails.value = response.data; // Сохраняем то, что пришло, для отладки
}
} catch (error) {
console.error('[VerifyAddress] Error verifying address via backend proxy:', error.response?.data || error.message);
verifiedAddressDetails.value = error.response?.data; // Сохраняем детали ошибки для отладки
addressVerificationResult.value = 'error';
} finally {
isAddressVerifying.value = false;
}
};
// --- Функции для управления списком выбранных кодов ISIC ---
const addIsicCode = () => {
if (currentSelectedIsicCode.value && currentSelectedIsicText.value) {
const alreadyExists = dleDeploymentSettings.selectedIsicCodes.find(c => c.code === currentSelectedIsicCode.value);
if (!alreadyExists) {
dleDeploymentSettings.selectedIsicCodes.push({
code: currentSelectedIsicCode.value,
text: currentSelectedIsicText.value
});
// Сбрасываем селекторы для выбора следующего кода
selectedSection.value = ''; // Это вызовет каскадную очистку
// currentSelectedIsicCode.value = ''; // Уже сбросится через watch(selectedSection)
// currentSelectedIsicText.value = '';
} else {
alert('Этот код уже добавлен.');
}
} else {
alert('Код не выбран полностью.');
}
};
const removeIsicCode = (index) => {
dleDeploymentSettings.selectedIsicCodes.splice(index, 1);
};
</script>
<style scoped>
@@ -78,11 +646,6 @@ const saveSettings = async (section) => {
margin-top: var(--spacing-lg);
animation: fadeIn var(--transition-normal);
}
h2 {
margin-bottom: var(--spacing-lg);
border-bottom: 1px solid var(--color-grey-light);
padding-bottom: var(--spacing-md);
}
h3 {
margin-bottom: var(--spacing-md);
color: var(--color-primary);
@@ -120,4 +683,136 @@ h3 {
from { opacity: 0; }
to { opacity: 1; }
}
.partner-entry {
border: 1px solid var(--color-grey-light);
padding: var(--spacing-md);
margin-bottom: var(--spacing-md);
border-radius: var(--radius-sm);
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.partner-entry .form-group {
margin-bottom: 0; /* Убираем лишний отступ у вложенных групп */
}
.btn-danger.btn-sm {
align-self: flex-start;
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.875rem; /* Меньший размер для кнопки удаления */
}
.btn-secondary {
align-self: flex-start;
margin-bottom: var(--spacing-md); /* Отступ после кнопки "Добавить партнера" */
}
.btn-lg {
padding: var(--spacing-md) var(--spacing-lg);
font-size: 1.125rem;
}
.address-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); /* Адаптивная сетка */
gap: var(--spacing-md);
/* margin-bottom: var(--spacing-lg); Убрано, т.к. есть блок верификации */
}
/* Можно добавить специфичные стили для полей адреса, если нужно */
.address-grid .form-group {
margin-bottom: 0; /* Убрать лишний отступ у полей в сетке */
}
.code-list {
margin-bottom: var(--spacing-md);
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.code-entry {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-grey-light);
border-radius: var(--radius-sm);
background-color: white;
}
.code-entry .btn-danger {
flex-shrink: 0;
margin-left: var(--spacing-md);
}
.add-code-form {
margin-top: var(--spacing-sm); /* Меньше отступ, т.к. он под списком */
}
.add-code-form .btn-secondary {
align-self: flex-start;
}
.address-verification-section {
margin-top: var(--spacing-xs); /* Небольшой отступ сверху */
margin-bottom: var(--spacing-lg); /* Отступ после секции верификации */
}
.verification-status p {
margin-bottom: var(--spacing-xs);
padding: var(--spacing-sm);
border-radius: var(--radius-sm);
}
.verification-status .alert-success {
background-color: var(--color-success-light, #e6fffa); /* Добавлены цвета по умолчанию */
color: var(--color-success-dark, #006d5b);
border: 1px solid var(--color-success-dark, #006d5b);
}
.verification-status .alert-warning {
background-color: var(--color-warning-light, #fff8e1);
color: var(--color-warning-dark, #8a6d3b);
border: 1px solid var(--color-warning-dark, #8a6d3b);
}
.verification-status .alert-danger {
background-color: var(--color-danger-light, #fdecea);
color: var(--color-danger-dark, #a94442);
border: 1px solid var(--color-danger-dark, #a94442);
}
.code-block {
background-color: #f5f5f5;
padding: var(--spacing-sm);
border-radius: var(--radius-sm);
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--color-grey-light, #e0e0e0);
}
.current-isic-selection {
padding: var(--spacing-sm);
background-color: var(--color-grey-x-light);
border-radius: var(--radius-sm);
margin-top: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.isic-codes-list ul {
list-style-type: none;
padding-left: 0;
}
.isic-codes-list li {
background-color: var(--color-background);
padding: var(--spacing-xs) var(--spacing-sm);
border: 1px solid var(--color-grey-light);
border-radius: var(--radius-sm);
}
.btn-xs {
padding: 0.2rem 0.4rem;
font-size: 0.75rem;
}
</style>

View File

@@ -1,124 +1,263 @@
<template>
<div class="security-settings settings-panel">
<h2>Настройки Безопасности</h2>
<h2>Настройки безопасности и подключения к блокчейну</h2>
<!-- Панель Авторизация -->
<div class="sub-settings-panel">
<h3>Настройки авторизации</h3>
<div class="setting-form">
<p>Управление параметрами авторизации</p>
<div class="form-group">
<label class="form-label">Метод авторизации по умолчанию:</label>
<select v-model="settings.defaultAuthMethod" class="form-control">
<option value="wallet">Кошелек</option>
<option value="email">Email</option>
<option value="telegram">Telegram</option>
</select>
<h3>RPC Провайдеры</h3>
<p>Добавьте конфигурации RPC для сетей, которые будут использоваться в приложении.</p>
<!-- Список добавленных RPC -->
<div v-if="securitySettings.rpcConfigs.length > 0" class="rpc-list">
<h4>Добавленные RPC конфигурации:</h4>
<div v-for="(rpc, index) in securitySettings.rpcConfigs" :key="index" class="rpc-entry">
<span><strong>ID Сети:</strong> {{ rpc.networkId }}</span>
<span><strong>URL:</strong> {{ rpc.rpcUrl }}</span>
<button class="btn btn-danger btn-sm" @click="removeRpcConfig(index)">Удалить</button>
</div>
<button class="btn btn-primary" @click="saveSettings('authorization')">Сохранить</button>
</div>
<p v-else>Нет добавленных RPC конфигураций.</p>
<!-- Форма добавления нового RPC -->
<div class="setting-form add-rpc-form">
<h4>Добавить новую RPC конфигурацию:</h4>
<div class="form-group">
<label class="form-label" for="newRpcNetworkId">ID Сети:</label>
<input type="text" id="newRpcNetworkId" v-model="newRpcEntry.networkId" class="form-control" placeholder="Например, polygon или sepolia">
<small>Этот ID должен совпадать со значением `value` в выпадающем списке сетей при создании DLE.</small>
</div>
<div class="form-group">
<label class="form-label" for="newRpcUrl">RPC URL:</label>
<input type="text" id="newRpcUrl" v-model="newRpcEntry.rpcUrl" class="form-control" placeholder="https://...">
</div>
<button class="btn btn-secondary" @click="addRpcConfig">Добавить RPC</button>
</div>
</div>
<!-- Панель RPC -->
<div class="sub-settings-panel">
<h3>Настройки RPC</h3>
<h3>Ключ Деплоера</h3>
<div class="setting-form">
<p>Настройка RPC-эндпоинтов</p>
<div class="form-group">
<label class="form-label">Ethereum RPC:</label>
<input type="text" v-model="settings.rpcEndpoints.ethereum" class="form-control">
<label class="form-label" for="deployerKey">Приватный ключ кошелька для деплоя:</label>
<input type="password" id="deployerKey" v-model="securitySettings.deployerPrivateKey" class="form-control" placeholder="0x...">
<small>Ключ будет скрыт. Не сохраняйте его в браузере.</small>
</div>
<div class="form-group">
<label class="form-label">Polygon RPC:</label>
<input type="text" v-model="settings.rpcEndpoints.polygon" class="form-control">
</div>
<button class="btn btn-primary" @click="saveSettings('rpc')">Сохранить</button>
</div>
</div>
<!-- Панель Noda -->
<div class="sub-settings-panel">
<h3>Настройки Noda</h3>
<h3>Ключ API Etherscan</h3>
<p>API ключ для Etherscan (и аналогов для других сетей) используется для автоматической верификации исходного кода смарт-контрактов после деплоя.</p>
<div class="setting-form">
<p>Конфигурация нод</p>
<div class="form-group">
<label class="form-label">
<input type="checkbox" v-model="settings.useCustomNode">
Использовать собственную ноду
</label>
<label class="form-label" for="etherscanApi">Etherscan API Key:</label>
<input type="text" id="etherscanApi" v-model="securitySettings.etherscanApiKey" class="form-control" placeholder="YourEtherscanApiKey">
</div>
<div class="form-group" v-if="settings.useCustomNode">
<label class="form-label">URL собственной ноды:</label>
<input type="text" v-model="settings.customNodeUrl" class="form-control">
</div>
<button class="btn btn-primary" @click="saveSettings('noda')">Сохранить</button>
<!-- TODO: Добавить поля для API ключей других обозревателей (Polygonscan, BscScan и т.д.) -->
</div>
</div>
<!-- Панель Хранилище -->
<div class="sub-settings-panel">
<h3>Настройки хранилища</h3>
<h3>База данных (PostgreSQL)</h3>
<div class="setting-form">
<p>Настройки для хранения данных</p>
<div class="form-group">
<label class="form-label">Тип хранилища:</label>
<select v-model="settings.storageType" class="form-control">
<option value="local">Локальное</option>
<option value="ipfs">IPFS</option>
<option value="arweave">Arweave</option>
</select>
<label class="form-label" for="dbHost">Хост:</label>
<input type="text" id="dbHost" v-model="securitySettings.dbHost" class="form-control" placeholder="postgres">
</div>
<div class="form-group">
<label class="form-label" for="dbPort">Порт:</label>
<input type="number" id="dbPort" v-model="securitySettings.dbPort" class="form-control" placeholder="5432">
</div>
<div class="form-group">
<label class="form-label" for="dbName">Имя БД:</label>
<input type="text" id="dbName" v-model="securitySettings.dbName" class="form-control" placeholder="dapp_db">
</div>
<div class="form-group">
<label class="form-label" for="dbUser">Пользователь БД:</label>
<input type="text" id="dbUser" v-model="securitySettings.dbUser" class="form-control" placeholder="dapp_user">
</div>
<div class="form-group">
<label class="form-label" for="dbPassword">Пароль БД:</label>
<input type="password" id="dbPassword" v-model="securitySettings.dbPassword" class="form-control" placeholder="********">
</div>
<button class="btn btn-primary" @click="saveSettings('storage')">Сохранить</button>
</div>
</div>
<!-- Панель CPU -->
<div class="sub-settings-panel">
<h3>Настройки CPU</h3>
<h3>Настройки Email</h3>
<div class="setting-form">
<p>Оптимизация использования CPU</p>
<div class="form-group">
<label class="form-label">Максимальное использование CPU (%):</label>
<input type="number" v-model="settings.maxCpuUsage" min="0" max="100" class="form-control">
<label class="form-label" for="emailSmtpHost">SMTP Хост:</label>
<input type="text" id="emailSmtpHost" v-model="securitySettings.emailSmtpHost" class="form-control" placeholder="smtp.example.com">
</div>
<button class="btn btn-primary" @click="saveSettings('cpu')">Сохранить</button>
<div class="form-group">
<label class="form-label" for="emailSmtpPort">SMTP Порт:</label>
<input type="number" id="emailSmtpPort" v-model="securitySettings.emailSmtpPort" class="form-control" placeholder="465">
</div>
<div class="form-group">
<label class="form-label" for="emailImapHost">IMAP Хост:</label>
<input type="text" id="emailImapHost" v-model="securitySettings.emailImapHost" class="form-control" placeholder="imap.example.com">
</div>
<div class="form-group">
<label class="form-label" for="emailImapPort">IMAP Порт:</label>
<input type="number" id="emailImapPort" v-model="securitySettings.emailImapPort" class="form-control" placeholder="993">
</div>
<div class="form-group">
<label class="form-label" for="emailUser">Email Пользователь:</label>
<input type="email" id="emailUser" v-model="securitySettings.emailUser" class="form-control" placeholder="your_email@example.com">
</div>
<div class="form-group">
<label class="form-label" for="emailPassword">Email Пароль:</label>
<input type="password" id="emailPassword" v-model="securitySettings.emailPassword" class="form-control" placeholder="********">
</div>
</div>
</div>
<div class="sub-settings-panel">
<h3>Telegram Бот</h3>
<div class="setting-form">
<div class="form-group">
<label class="form-label" for="telegramToken">Токен Бота:</label>
<input type="text" id="telegramToken" v-model="securitySettings.telegramBotToken" class="form-control" placeholder="123456:ABC-DEF1234...">
<small>Получите у @BotFather в Telegram.</small>
</div>
<div class="form-group">
<label class="form-label" for="telegramUsername">Имя пользователя Бота:</label>
<input type="text" id="telegramUsername" v-model="securitySettings.telegramBotUsername" class="form-control" placeholder="YourTelegramBot">
</div>
</div>
</div>
<div class="sub-settings-panel">
<h3>Прочие настройки</h3>
<div class="setting-form">
<div class="form-group">
<label class="form-label" for="sessionSecret">Секрет сессии:</label>
<input type="password" id="sessionSecret" v-model="securitySettings.sessionSecret" class="form-control" placeholder="Очень длинный и случайный секрет">
<small>Используется для подписи cookie сессий. Должен быть надежным.</small>
</div>
<div class="form-group">
<label class="form-label" for="frontendUrl">Frontend URL:</label>
<input type="text" id="frontendUrl" v-model="securitySettings.frontendUrl" class="form-control" placeholder="http://localhost:5173">
<small>URL, по которому доступен фронтенд (для CORS, ссылок и т.д.).</small>
</div>
</div>
</div>
<div class="sub-settings-panel save-panel">
<button class="btn btn-primary btn-lg" @click="saveSecuritySettings">Сохранить настройки безопасности</button>
</div>
</div>
</template>
<script setup>
import { reactive, onMounted } from 'vue';
// TODO: Импортировать API
import { reactive } from 'vue';
const settings = reactive({
defaultAuthMethod: 'wallet',
rpcEndpoints: {
ethereum: '',
polygon: ''
},
useCustomNode: false,
customNodeUrl: '',
storageType: 'local',
maxCpuUsage: 80
// TODO: Импортировать API для сохранения/загрузки настроек (если это безопасно реализуемо)
const securitySettings = reactive({
rpcConfigs: [], // Массив для хранения { networkId: string, rpcUrl: string }
deployerPrivateKey: '',
etherscanApiKey: '',
// Настройки БД
dbHost: '',
dbPort: 5432,
dbName: '',
dbUser: '',
dbPassword: '',
// Настройки Email
emailSmtpHost: '',
emailSmtpPort: 465,
emailImapHost: '',
emailImapPort: 993,
emailUser: '',
emailPassword: '',
// Настройки Telegram
telegramBotToken: '',
telegramBotUsername: '',
// Прочие настройки
sessionSecret: '',
frontendUrl: '',
});
onMounted(() => {
loadSecuritySettings();
// Реактивный объект для новой записи RPC
const newRpcEntry = reactive({
networkId: '',
rpcUrl: ''
});
const loadSecuritySettings = async () => {
console.log('[SecuritySettingsView] Загрузка настроек безопасности...');
// TODO: API call
// Функция добавления новой RPC конфигурации
const addRpcConfig = () => {
const networkId = newRpcEntry.networkId.trim();
const rpcUrl = newRpcEntry.rpcUrl.trim();
if (networkId && rpcUrl) {
// Проверка на дубликат ID (опционально, но полезно)
if (securitySettings.rpcConfigs.some(rpc => rpc.networkId === networkId)) {
alert(`Ошибка: RPC конфигурация для сети с ID '${networkId}' уже существует.`);
return;
}
securitySettings.rpcConfigs.push({ networkId, rpcUrl });
// Очистка полей ввода
newRpcEntry.networkId = '';
newRpcEntry.rpcUrl = '';
} else {
alert('Пожалуйста, заполните ID Сети и RPC URL.');
}
};
const saveSettings = async (section) => {
console.log(`[SecuritySettingsView] Сохранение настроек раздела: ${section}`);
// TODO: API call
// Функция удаления RPC конфигурации
const removeRpcConfig = (index) => {
securitySettings.rpcConfigs.splice(index, 1);
};
const saveSecuritySettings = async () => {
// ВАЖНО: Отправка приватного ключа на бэкенд или его сохранение
// через UI - КРАЙНЕ ОПАСНО.
// Идеально - настроить его через .env или защищенный серверный механизм.
// Эта функция - заглушка. В реальном приложении нужна безопасная логика.
if (securitySettings.deployerPrivateKey) {
console.warn('[SecuritySettingsView] Попытка сохранить приватный ключ через UI! Это небезопасно!');
alert('ПРЕДУПРЕЖДЕНИЕ: Сохранение приватного ключа через веб-интерфейс КРАЙНЕ НЕ РЕКОМЕНДУЕТСЯ из соображений безопасности. Используйте .env файл или переменные окружения на сервере.');
}
console.log('[SecuritySettingsView] Попытка сохранения настроек:', {
rpcConfigs: JSON.parse(JSON.stringify(securitySettings.rpcConfigs)), // Логируем копию массива
etherscanApiKey: securitySettings.etherscanApiKey ? '******' : 'Не указан', // Тоже чувствительные данные
deployerPrivateKey: securitySettings.deployerPrivateKey ? '******' : 'Не указан', // Не логируем сам ключ!
dbHost: securitySettings.dbHost,
dbPort: securitySettings.dbPort,
dbName: securitySettings.dbName,
dbUser: securitySettings.dbUser,
dbPassword: securitySettings.dbPassword ? '******' : 'Не указан',
emailSmtpHost: securitySettings.emailSmtpHost,
emailSmtpPort: securitySettings.emailSmtpPort,
emailImapHost: securitySettings.emailImapHost,
emailImapPort: securitySettings.emailImapPort,
emailUser: securitySettings.emailUser,
emailPassword: securitySettings.emailPassword ? '******' : 'Не указан',
telegramBotToken: securitySettings.telegramBotToken ? '******' : 'Не указан',
telegramBotUsername: securitySettings.telegramBotUsername,
sessionSecret: securitySettings.sessionSecret ? '******' : 'Не указан',
frontendUrl: securitySettings.frontendUrl,
});
// TODO: Реализовать безопасную отправку данных (кроме ключа?) на бэкенд
// await api.saveSecurityConfig({
// rpcUrls: {
// polygon: securitySettings.polygonRpcUrl,
// ethereum: securitySettings.ethRpcUrl,
// bsc: securitySettings.bscRpcUrl,
// arbitrum: securitySettings.arbitrumRpcUrl,
// default: securitySettings.defaultRpcUrl,
// }
// etherscanApiKey: securitySettings.etherscanApiKey
// // НЕ передавать приватный ключ напрямую!
// });
alert('Настройки (заглушка) сохранены. Проверьте консоль. Обратите внимание на предупреждения о приватном ключе.');
};
</script>
<style scoped>
/* Используем стили, похожие на BlockchainSettingsView */
.settings-panel {
padding: var(--block-padding);
background-color: var(--color-light);
@@ -140,7 +279,8 @@ h3 {
padding-bottom: var(--spacing-lg);
border-bottom: 1px dashed var(--color-grey-light);
}
.sub-settings-panel:last-child {
.sub-settings-panel:last-child,
.save-panel {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
@@ -154,18 +294,128 @@ h3 {
margin-bottom: 0;
}
.form-label {
display: block; /* Чтобы занимала всю ширину */
margin-bottom: var(--spacing-xs);
font-weight: 500;
}
.form-control {
max-width: 600px; /* Немного шире для ключей и URL */
padding: var(--spacing-sm);
border: 1px solid var(--color-grey-light);
border-radius: var(--radius-sm);
font-size: 1rem;
}
.form-control[type="password"] {
font-family: monospace; /* Чтобы легче было вводить ключ */
}
small {
display: block;
margin-top: var(--spacing-xs);
color: var(--color-grey-dark);
}
.warning-block {
border: 2px solid var(--color-danger);
background-color: var(--color-danger-light);
color: var(--color-danger-dark);
padding: var(--spacing-md);
border-radius: var(--radius-sm);
margin-bottom: var(--spacing-md);
}
.warning-block h4 {
color: var(--color-danger);
margin-top: 0;
margin-bottom: var(--spacing-sm);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.form-control {
max-width: 500px;
.warning-block p {
margin-bottom: var(--spacing-sm);
}
.warning-block p:last-child {
margin-bottom: 0;
}
.btn-lg {
padding: var(--spacing-md) var(--spacing-lg);
font-size: 1.125rem;
}
.btn-primary {
align-self: flex-start;
background-color: var(--color-primary);
color: white;
border: none;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-sm);
cursor: pointer;
transition: background-color var(--transition-fast);
}
.btn-primary:hover {
background-color: var(--color-primary-dark);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Предполагаем, что у вас есть иконки FontAwesome или аналог */
/* @import url('.../path/to/fontawesome.css'); */
/* Если иконок нет, можно убрать тег <i> */
.fa-exclamation-triangle {
color: var(--color-danger);
}
.rpc-list {
margin-top: var(--spacing-md);
margin-bottom: var(--spacing-lg);
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.rpc-entry {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-sm);
border: 1px solid var(--color-grey-light);
border-radius: var(--radius-sm);
background-color: white; /* Немного выделить фон */
}
.rpc-entry span {
flex-grow: 1;
}
.rpc-entry .btn-danger {
flex-shrink: 0;
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 0.875rem;
}
.add-rpc-form {
margin-top: var(--spacing-lg);
padding-top: var(--spacing-lg);
border-top: 1px dashed var(--color-grey-light);
}
.add-rpc-form h4 {
margin-bottom: var(--spacing-md);
}
.add-rpc-form .btn-secondary {
align-self: flex-start;
background-color: var(--color-secondary); /* Пример цвета */
color: white;
border: none;
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-sm);
cursor: pointer;
transition: background-color var(--transition-fast);
}
.add-rpc-form .btn-secondary:hover {
background-color: var(--color-secondary-dark); /* Пример цвета */
}
</style>