mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
VIN Numbers can be decoded.
This commit is contained in:
parent
e5fe1b6bb9
commit
f2a4eb930c
8 changed files with 195 additions and 4 deletions
|
|
@ -825,3 +825,15 @@ export async function uploadFiles(files, destination) {
|
|||
data.append('destination', destination);
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
export async function fetchDecoded(vinNumber) {
|
||||
const body = {vin_number: vinNumber};
|
||||
const response = await apiPost(`${API_URI}/vin_number_decoder`, body);
|
||||
if (response.status === 200) {
|
||||
const content = await response.json();
|
||||
return content['vin'];
|
||||
} else if (response.status === 400) {
|
||||
console.error('Invalid VIN Number');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React, {useContext} from 'react';
|
||||
import {Divider, SegmentGroup, StatisticLabel, StatisticValue} from "semantic-ui-react";
|
||||
import {Divider, Input, SegmentGroup, StatisticLabel, StatisticValue, TableCell, TableRow} from "semantic-ui-react";
|
||||
import {Link, Route, Routes} from "react-router-dom";
|
||||
import {decryptOTP, encryptOTP} from "../api";
|
||||
import {humanFileSize, mimetypeColor, PageContainer, toLocaleString, useTitle} from "./Common";
|
||||
import {ThemeContext} from "../contexts/contexts";
|
||||
import {Button, Header, Loader, Segment, Statistic, StatisticGroup, TextArea} from "./Theme";
|
||||
import {useStatistics} from "../hooks/customHooks";
|
||||
import {Button, Header, Loader, Segment, Statistic, StatisticGroup, Table, TextArea} from "./Theme";
|
||||
import {useStatistics, useVINDecoder} from "../hooks/customHooks";
|
||||
|
||||
class Encrypt extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -145,6 +145,57 @@ function OTP() {
|
|||
</>
|
||||
}
|
||||
|
||||
function VINDecoder() {
|
||||
useTitle('VIN Number Decoder');
|
||||
const {t} = useContext(ThemeContext);
|
||||
|
||||
const basicKeys = ['Country', 'Manufacturer', 'Region', 'Years'];
|
||||
const detailsKeys = ['Body', 'Engine', 'Model', 'Plant', 'Transmission', 'Serial'];
|
||||
|
||||
const {value, setValue, vin} = useVINDecoder();
|
||||
console.debug(vin);
|
||||
|
||||
let body = <p {...t}>Enter a VIN number above</p>;
|
||||
if (value && !vin) {
|
||||
body = <p {...t}>VIN number is invalid</p>;
|
||||
}
|
||||
if (vin && vin['country']) {
|
||||
let details = <p {...t}>No details</p>;
|
||||
if (vin['body']) {
|
||||
details = <Table celled columns={2} {...t}>
|
||||
{detailsKeys.map(i => <TableRow key={i}>
|
||||
<TableCell width={5}><b>{i}</b></TableCell>
|
||||
<TableCell width={11}>{vin[i.toLowerCase()] || '(Unknown)'}</TableCell>
|
||||
</TableRow>)}
|
||||
</Table>;
|
||||
}
|
||||
body = <>
|
||||
<Table celled columns={2} {...t}>
|
||||
{basicKeys.map(i => <TableRow key={i}>
|
||||
<TableCell width={5}><b>{i}</b></TableCell>
|
||||
<TableCell width={11}>{vin[i.toLowerCase()]}</TableCell>
|
||||
</TableRow>)}
|
||||
</Table>
|
||||
<Header as='h3'>Details</Header>
|
||||
{details}
|
||||
</>;
|
||||
}
|
||||
|
||||
return <>
|
||||
<Header as='h1'>VIN Number Decoder</Header>
|
||||
<Input
|
||||
size='large'
|
||||
label='VIN'
|
||||
value={value}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
/>
|
||||
|
||||
<Header as='h2'>Decoded</Header>
|
||||
{body}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
function StatisticsPage() {
|
||||
useTitle('Statistics');
|
||||
|
||||
|
|
@ -251,6 +302,7 @@ export function MoreRoute(props) {
|
|||
<Routes>
|
||||
<Route path='otp' exact element={<OTP/>}/>
|
||||
<Route path='statistics' exact element={<StatisticsPage/>}/>
|
||||
<Route path='vin' exact element={<VINDecoder/>}/>
|
||||
</Routes>
|
||||
</PageContainer>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const links = [
|
|||
links: [
|
||||
{to: '/more/otp', text: 'One Time Pad', end: true},
|
||||
{to: '/more/statistics', text: 'Statistics', end: true},
|
||||
{to: '/more/vin', text: 'VIN Decoder', end: true},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {useContext, useEffect, useRef, useState} from "react";
|
||||
import {
|
||||
fetchDecoded,
|
||||
fetchDomains,
|
||||
fetchFilesProgress,
|
||||
filesSearch,
|
||||
|
|
@ -928,3 +929,29 @@ export const useUploadFile = () => {
|
|||
|
||||
return {files, setFiles: handleFilesChange, progresses, destination, setDestination, doClear, doUpload}
|
||||
}
|
||||
|
||||
export const useVINDecoder = (defaultVINNumber = '') => {
|
||||
const [value, setValue] = useState(defaultVINNumber);
|
||||
// The response vin from the API.
|
||||
const [vin, setVin] = useState(null);
|
||||
|
||||
const localFetchDecoded = async () => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const vin = await fetchDecoded(value);
|
||||
setVin(vin);
|
||||
} catch (e) {
|
||||
console.error('Failed to decode VIN number');
|
||||
setVin(null);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
localFetchDecoded();
|
||||
}, [value]);
|
||||
|
||||
return {value, setValue, vin}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ sanic-testing==22.6.0
|
|||
sanic==22.6.2
|
||||
selenium==4.9.1
|
||||
srt==3.5.3
|
||||
vininfo[cli]==1.7.0
|
||||
websockets==10.1
|
||||
webvtt-py==0.4.6
|
||||
yt-dlp==2023.3.4
|
||||
yt-dlp==2023.3.4
|
||||
|
|
@ -7,12 +7,15 @@ from http import HTTPStatus
|
|||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import vininfo.exceptions
|
||||
from sanic import Sanic, response, Blueprint, __version__ as sanic_version
|
||||
from sanic.blueprint_group import BlueprintGroup
|
||||
from sanic.request import Request
|
||||
from sanic.response import HTTPResponse
|
||||
from sanic_ext import validate
|
||||
from sanic_ext.extensions.openapi import openapi
|
||||
from vininfo import Vin
|
||||
from vininfo.details._base import VinDetails
|
||||
|
||||
from wrolpi import admin, status, flags, schema
|
||||
from wrolpi import tags
|
||||
|
|
@ -421,6 +424,39 @@ async def post_notify(_: Request, body: schema.NotifyRequest):
|
|||
return response.empty(HTTPStatus.CREATED)
|
||||
|
||||
|
||||
@api_bp.post('/vin_number_decoder')
|
||||
@validate(schema.VINDecoderRequest)
|
||||
async def post_vin_number_decoder(_: Request, body: schema.VINDecoderRequest):
|
||||
try:
|
||||
vin = Vin(body.vin_number)
|
||||
except vininfo.exceptions.VininfoException:
|
||||
return json_response({}, HTTPStatus.BAD_REQUEST)
|
||||
|
||||
def detail_to_json(details: VinDetails, key: str):
|
||||
if details:
|
||||
attr = getattr(details, key)
|
||||
if attr and attr.name:
|
||||
name = attr.name
|
||||
if isinstance(name, list):
|
||||
return ', '.join(str(i) for i in name)
|
||||
else:
|
||||
return name
|
||||
|
||||
vin = schema.VIN(
|
||||
country=vin.country,
|
||||
manufacturer=vin.manufacturer,
|
||||
region=vin.region,
|
||||
years=','.join(map(str, vin.years)),
|
||||
body=detail_to_json(vin.details, 'body'),
|
||||
engine=detail_to_json(vin.details, 'engine'),
|
||||
model=detail_to_json(vin.details, 'model'),
|
||||
plant=detail_to_json(vin.details, 'plant'),
|
||||
transmission=detail_to_json(vin.details, 'transmission'),
|
||||
serial=detail_to_json(vin.details, 'serial'),
|
||||
)
|
||||
return json_response(dict(vin=vin))
|
||||
|
||||
|
||||
class CustomJSONEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
|
|
|
|||
|
|
@ -82,3 +82,41 @@ class DeleteTagRequest:
|
|||
class NotifyRequest:
|
||||
message: str
|
||||
url: str = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class VINDecoderRequest:
|
||||
vin_number: str
|
||||
|
||||
@dataclass
|
||||
class VIN:
|
||||
country: str
|
||||
manufacturer: str
|
||||
region: str
|
||||
years: str
|
||||
body: str = None
|
||||
engine: str = None
|
||||
model: str = None
|
||||
plant: str = None
|
||||
transmission: str = None
|
||||
serial: str = None
|
||||
|
||||
def __json__(self):
|
||||
d = dict(
|
||||
country=self.country,
|
||||
manufacturer=self.manufacturer,
|
||||
region=self.region,
|
||||
years=self.years,
|
||||
body=self.body,
|
||||
engine=self.engine,
|
||||
model=self.model,
|
||||
plant=self.plant,
|
||||
transmission=self.transmission,
|
||||
serial=self.serial,
|
||||
)
|
||||
return d
|
||||
|
||||
|
||||
@dataclass
|
||||
class VINDecoderResponse:
|
||||
vin: VIN
|
||||
|
|
|
|||
|
|
@ -358,3 +358,27 @@ def test_get_downloaders(test_client):
|
|||
def test_get_global_statistics(test_session, test_client):
|
||||
request, response = test_client.get('/api/statistics')
|
||||
assert response.json['global_statistics']['db_size'] > 1
|
||||
|
||||
|
||||
def test_post_vin_number_decoder(test_client):
|
||||
"""A VIN number can be decoded."""
|
||||
# Invalid VIN number.
|
||||
content = dict(vin_number='5N1NJ01CXST00000')
|
||||
request, response = test_client.post('/api/vin_number_decoder', content=json.dumps(content))
|
||||
assert response.status_code == HTTPStatus.BAD_REQUEST
|
||||
assert response.json == dict()
|
||||
|
||||
# Test VIN from vininfo.
|
||||
content = dict(vin_number='5N1NJ01CXST000001')
|
||||
request, response = test_client.post('/api/vin_number_decoder', content=json.dumps(content))
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
assert response.json['vin'] == {'body': 'Sedan, 4-Door, Standard Body Truck',
|
||||
'country': 'United States',
|
||||
'engine': 'VH45DE',
|
||||
'manufacturer': 'Nissan',
|
||||
'model': 'Maxima',
|
||||
'plant': 'Tochigi, Oppama',
|
||||
'region': 'North America',
|
||||
'serial': None,
|
||||
'transmission': None,
|
||||
'years': '1995'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue