VIN Numbers can be decoded.

This commit is contained in:
Roland 2023-06-11 12:31:56 -06:00
parent e5fe1b6bb9
commit f2a4eb930c
No known key found for this signature in database
GPG key ID: 9D6DBD3ACF11AB93
8 changed files with 195 additions and 4 deletions

View file

@ -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;
}
}

View file

@ -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>
}

View file

@ -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},
]
},
];

View file

@ -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}
}

View file

@ -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

View file

@ -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):

View file

@ -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

View file

@ -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'}