Skip to content

Commit 3dc0624

Browse files
authored
Settings Mega Branch (#191)
1 parent e1d75b8 commit 3dc0624

112 files changed

Lines changed: 12059 additions & 3921 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

TELEMETRY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Events follow the pattern: `<feature>_<action>`
4040
- `settings_name_clear` - User clears their preferred name
4141
- `settings_location_set` - User sets their location initially
4242
- `settings_location_update` - User updates their location
43+
- `settings_localization_update` - User updates localization settings (temperature, wind speed, precipitation, time format, language)
4344
- `settings_database_reset` - User resets the application database
4445
- `settings_data_collection_enabled` - User enables data collection
4546
- `settings_data_collection_disabled` - User disables data collection

backend/.env.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
EXA_API_KEY=""

backend/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
.env
2-
.env.test
32
server

backend/src/api/routes.test.ts

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,149 @@ describe('Main Routes', () => {
9292
const data = await response.json()
9393
expect(Array.isArray(data)).toBe(true)
9494
})
95-
})
9695

96+
describe('Units routes', () => {
97+
it('should require country parameter for units endpoint', async () => {
98+
const response = await app.handle(new Request('http://localhost/v1/units'))
99+
expect(response.status).toBe(422) // Elysia validation error
100+
})
101+
102+
it('should return units data for valid country code (Brazil)', async () => {
103+
const response = await app.handle(new Request('http://localhost/v1/units?country=BR'))
104+
expect(response.status).toBe(200)
105+
106+
const data = await response.json()
107+
expect(data).toEqual({
108+
unit: 'metric',
109+
temperature: 'c',
110+
timeFormat: '24h',
111+
dateFormatExample: 'DD/MM/YYYY',
112+
currency: {
113+
code: 'BRL',
114+
symbol: 'R$',
115+
name: 'Brazilian Real',
116+
},
117+
})
118+
})
119+
120+
it('should return units data for valid country name (Brazil)', async () => {
121+
const response = await app.handle(new Request('http://localhost/v1/units?country=Brazil'))
122+
expect(response.status).toBe(200)
123+
124+
const data = await response.json()
125+
expect(data).toEqual({
126+
unit: 'metric',
127+
temperature: 'c',
128+
timeFormat: '24h',
129+
dateFormatExample: 'DD/MM/YYYY',
130+
currency: {
131+
code: 'BRL',
132+
symbol: 'R$',
133+
name: 'Brazilian Real',
134+
},
135+
})
136+
})
137+
138+
it('should return units data for valid country code (United States)', async () => {
139+
const response = await app.handle(new Request('http://localhost/v1/units?country=US'))
140+
expect(response.status).toBe(200)
141+
142+
const data = await response.json()
143+
expect(data).toEqual({
144+
unit: 'imperial',
145+
temperature: 'f',
146+
timeFormat: '12h',
147+
dateFormatExample: 'MM/DD/YYYY',
148+
currency: {
149+
code: 'USD',
150+
symbol: '$',
151+
name: 'US Dollar',
152+
},
153+
})
154+
})
155+
156+
it('should return units data for valid country name (United States)', async () => {
157+
const response = await app.handle(new Request('http://localhost/v1/units?country=United States'))
158+
expect(response.status).toBe(200)
159+
160+
const data = await response.json()
161+
expect(data).toEqual({
162+
unit: 'imperial',
163+
temperature: 'f',
164+
timeFormat: '12h',
165+
dateFormatExample: 'MM/DD/YYYY',
166+
currency: {
167+
code: 'USD',
168+
symbol: '$',
169+
name: 'US Dollar',
170+
},
171+
})
172+
})
173+
174+
it('should return US data as fallback for invalid country', async () => {
175+
const response = await app.handle(new Request('http://localhost/v1/units?country=INVALID'))
176+
expect(response.status).toBe(200)
177+
178+
const data = await response.json()
179+
expect(data).toEqual({
180+
unit: 'imperial',
181+
temperature: 'f',
182+
timeFormat: '12h',
183+
dateFormatExample: 'MM/DD/YYYY',
184+
currency: {
185+
code: 'USD',
186+
symbol: '$',
187+
name: 'US Dollar',
188+
},
189+
})
190+
})
191+
192+
it('should return 400 for empty country parameter', async () => {
193+
const response = await app.handle(new Request('http://localhost/v1/units?country='))
194+
expect(response.status).toBe(400)
195+
196+
const data = await response.text()
197+
expect(data).toBe('Country parameter is required')
198+
})
199+
200+
it('should return units-options data', async () => {
201+
const response = await app.handle(new Request('http://localhost/v1/units-options'))
202+
expect(response.status).toBe(200)
203+
204+
const data = await response.json()
205+
expect(data).toHaveProperty('units')
206+
expect(data).toHaveProperty('temperature')
207+
expect(data).toHaveProperty('timeFormat')
208+
expect(data).toHaveProperty('dateFormats')
209+
expect(data).toHaveProperty('currencies')
210+
211+
// Verify structure of units options
212+
expect(Array.isArray(data.units)).toBe(true)
213+
expect(data.units).toContain('metric')
214+
expect(data.units).toContain('imperial')
215+
216+
expect(Array.isArray(data.temperature)).toBe(true)
217+
expect(data.temperature).toEqual(
218+
expect.arrayContaining([
219+
expect.objectContaining({ symbol: 'c', name: 'Celsius' }),
220+
expect.objectContaining({ symbol: 'f', name: 'Fahrenheit' }),
221+
]),
222+
)
223+
224+
expect(Array.isArray(data.timeFormat)).toBe(true)
225+
expect(data.timeFormat).toContain('12h')
226+
expect(data.timeFormat).toContain('24h')
227+
228+
expect(Array.isArray(data.dateFormats)).toBe(true)
229+
expect(data.dateFormats.length).toBeGreaterThan(0)
230+
expect(data.dateFormats[0]).toHaveProperty('format')
231+
expect(data.dateFormats[0]).toHaveProperty('example')
232+
233+
expect(Array.isArray(data.currencies)).toBe(true)
234+
expect(data.currencies.length).toBeGreaterThan(0)
235+
expect(data.currencies[0]).toHaveProperty('code')
236+
expect(data.currencies[0]).toHaveProperty('symbol')
237+
expect(data.currencies[0]).toHaveProperty('name')
238+
})
239+
})
240+
})

backend/src/api/routes.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { Elysia, t } from 'elysia'
2+
import unitsByCountryData from '../data/localization/units-by-country.json'
3+
import unitsOptionsData from '../data/localization/units-options.json'
4+
import { resolveCountryCode } from '../utils/country'
25

36
export interface LocationResult {
47
name: string
@@ -17,6 +20,42 @@ export const createMainRoutes = () => {
1720
status: 'ok',
1821
}))
1922

23+
.get(
24+
'/units',
25+
(ctx) => {
26+
const { query, set } = ctx
27+
const country = query.country
28+
29+
if (!country) {
30+
set.status = 400
31+
throw new Error('Country parameter is required')
32+
}
33+
34+
const countryCode = resolveCountryCode(country)
35+
36+
if (!countryCode) {
37+
return unitsByCountryData.US
38+
}
39+
40+
const countryData = unitsByCountryData[countryCode as keyof typeof unitsByCountryData]
41+
42+
if (!countryData) {
43+
return unitsByCountryData.US
44+
}
45+
46+
return countryData
47+
},
48+
{
49+
query: t.Object({
50+
country: t.String(),
51+
}),
52+
},
53+
)
54+
55+
.get('/units-options', () => {
56+
return unitsOptionsData
57+
})
58+
2059
.get(
2160
'/locations',
2261
async (ctx): Promise<LocationResult[]> => {

0 commit comments

Comments
 (0)