mirror of
https://github.com/kirameki-cafe/Tenki.git
synced 2026-09-13 18:58:58 +00:00
♻️ refactor!: migrates to Bun runtime
This commit is contained in:
+56
-65
@@ -1,8 +1,13 @@
|
||||
import express from "express";
|
||||
import axios from "axios";
|
||||
import { Elysia } from "elysia";
|
||||
import { cors } from "@elysiajs/cors";
|
||||
|
||||
const app = express();
|
||||
const port = 3000;
|
||||
const app = new Elysia()
|
||||
.use(cors({
|
||||
origin: true,
|
||||
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
credentials: true,
|
||||
allowedHeaders: ["Content-Type", "Authorization"]
|
||||
}));
|
||||
|
||||
enum IpLookupStatus {
|
||||
success = "success",
|
||||
@@ -50,41 +55,19 @@ interface WeatherLookupCache {
|
||||
};
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
data?: {
|
||||
country: string;
|
||||
country_code: string;
|
||||
region: string;
|
||||
region_name: string;
|
||||
city: string;
|
||||
timezone: string;
|
||||
current_weather: {
|
||||
temperature: number;
|
||||
wind_speed: number;
|
||||
wind_direction: number;
|
||||
weather_code: number;
|
||||
is_day: boolean;
|
||||
time: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const geoIpCacheTime = 1000 * 60 * 60 * 24; // 24 hours
|
||||
const weatherCacheTime = 1000 * 60 * 15; // 15 minutes
|
||||
const geoIpCacheTime = 1000 * 60 * 60 * 24;
|
||||
const weatherCacheTime = 1000 * 60 * 15;
|
||||
const geoIpLookupCache: GeoIpLookupCache = {};
|
||||
const weatherLookupCache: WeatherLookupCache = {};
|
||||
|
||||
const isReservedIpRange = (ipAddress: string) => {
|
||||
// Check for IPv4
|
||||
const ipv4Pattern =
|
||||
/^(10\..*|172\.(1[6-9]|2[0-9]|3[0-1])\..*|192\.168\..*|127\..*|169\.254\..*|192\.88\.99\..*)$/;
|
||||
if (ipv4Pattern.test(ipAddress)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for IPv6
|
||||
const ipv6LocalPattern =
|
||||
/^fe[89ab][0-9a-fA-F]:.*|^::1$|^fc[0-9a-fA-F]{2}:.*$/;
|
||||
if (ipv6LocalPattern.test(ipAddress.toLowerCase())) {
|
||||
@@ -95,8 +78,8 @@ const isReservedIpRange = (ipAddress: string) => {
|
||||
};
|
||||
|
||||
const getGeoIp = async (ip: string) => {
|
||||
const response = await axios.get(`http://ip-api.com/json/${ip}`);
|
||||
let data = response.data;
|
||||
const response = await fetch(`http://ip-api.com/json/${ip}`);
|
||||
let data = await response.json();
|
||||
|
||||
data.cacheExpireAt = new Date(Date.now() + geoIpCacheTime);
|
||||
geoIpLookupCache[ip] = data;
|
||||
@@ -105,10 +88,10 @@ const getGeoIp = async (ip: string) => {
|
||||
};
|
||||
|
||||
const getWeather = async (lat: number, lon: number) => {
|
||||
const response = await axios.get(
|
||||
const response = await fetch(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t_weather=true`
|
||||
);
|
||||
let data = response.data;
|
||||
let data = await response.json();
|
||||
|
||||
data.cacheExpireAt = new Date(Date.now() + weatherCacheTime);
|
||||
weatherLookupCache[`${lat},${lon}`] = data;
|
||||
@@ -116,59 +99,65 @@ const getWeather = async (lat: number, lon: number) => {
|
||||
return data;
|
||||
};
|
||||
|
||||
const sendResponse = (res: any, data: ApiResponse) => {
|
||||
res.json(data);
|
||||
};
|
||||
|
||||
app.get("/", async (req, res) => {
|
||||
// Get the user's IP address
|
||||
let ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
|
||||
ip = ip?.toString().split(",")[0];
|
||||
app.get("/", async ({ server, request, set, headers }) => {
|
||||
let clientIp =
|
||||
headers['cf-connecting-ip'] ||
|
||||
headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
||||
headers['x-real-ip'] ||
|
||||
server?.requestIP(request)?.address;
|
||||
|
||||
if (!ip) return res.status(500).send("Error fetching location data");
|
||||
|
||||
// Check if the IP address is in a reserved range, assume Japan IP if so (Linode IP address)
|
||||
if (isReservedIpRange(ip)) {
|
||||
ip = "139.162.65.37";
|
||||
if (!clientIp || clientIp === "::1" || clientIp === "127.0.0.1" || clientIp === "localhost" || clientIp === "::ffff:127.0.0.1" || isReservedIpRange(clientIp)) {
|
||||
set.status = 400;
|
||||
return { success: false, error: "Invalid or local IP address. External access required." };
|
||||
}
|
||||
|
||||
// Check if geo IP is cached
|
||||
let geoIp = geoIpLookupCache[ip];
|
||||
let geoIp: typeof geoIpLookupCache[string] | undefined = geoIpLookupCache[clientIp];
|
||||
if (geoIp) {
|
||||
// Check if cached response is expired
|
||||
if (geoIp.cacheExpireAt < new Date()) {
|
||||
delete geoIpLookupCache[ip];
|
||||
delete geoIpLookupCache[clientIp];
|
||||
geoIp = undefined;
|
||||
}
|
||||
} else {
|
||||
console.log("Fetching GeoIP fresh response");
|
||||
}
|
||||
|
||||
if (!geoIp) {
|
||||
try {
|
||||
geoIp = await getGeoIp(ip);
|
||||
geoIp = await getGeoIp(clientIp);
|
||||
} catch (e) {
|
||||
return res
|
||||
.status(500)
|
||||
.json({ success: false, error: "Error fetching location data" });
|
||||
set.status = 500;
|
||||
return { success: false, error: "Error fetching location data" };
|
||||
}
|
||||
}
|
||||
|
||||
// Check if weather is cached
|
||||
let weather = weatherLookupCache[`${geoIp.lat},${geoIp.lon}`];
|
||||
if (!geoIp) {
|
||||
set.status = 500;
|
||||
return { success: false, error: "Error fetching location data" };
|
||||
}
|
||||
|
||||
let weather: typeof weatherLookupCache[string] | undefined = weatherLookupCache[`${geoIp.lat},${geoIp.lon}`];
|
||||
if (weather) {
|
||||
// Check if cached response is expired
|
||||
if (weather.cacheExpireAt < new Date()) {
|
||||
delete weatherLookupCache[`${geoIp.lat},${geoIp.lon}`];
|
||||
weather = undefined;
|
||||
}
|
||||
} else {
|
||||
console.log("Fetching weather fresh response");
|
||||
}
|
||||
|
||||
if (!weather) {
|
||||
try {
|
||||
weather = await getWeather(geoIp.lat, geoIp.lon);
|
||||
} catch (e) {
|
||||
return res
|
||||
.status(500)
|
||||
.json({ success: false, error: "Error fetching weather data" });
|
||||
set.status = 500;
|
||||
return { success: false, error: "Error fetching weather data" };
|
||||
}
|
||||
}
|
||||
|
||||
return sendResponse(res, {
|
||||
if (!weather) {
|
||||
set.status = 500;
|
||||
return { success: false, error: "Error fetching weather data" };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
country: geoIp.country,
|
||||
@@ -186,9 +175,11 @@ app.get("/", async (req, res) => {
|
||||
time: weather.current_weather.time,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server running at port: ${port}`);
|
||||
app.listen(3000, () => {
|
||||
console.log("🦊 Elysia is running at http://localhost:3000");
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
Reference in New Issue
Block a user