Add optional URL field #14
6 changed files with 44 additions and 15 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -654,7 +654,7 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huellas"
|
name = "huellas"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_db_pools",
|
"rocket_db_pools",
|
||||||
|
|
|
||||||
1
migrations/20230315004723_add_url.sql
Normal file
1
migrations/20230315004723_add_url.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE places ADD url VARCHAR DEFAULT null;
|
||||||
|
|
@ -11,4 +11,5 @@ pub struct Place {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub longitude: f64,
|
pub longitude: f64,
|
||||||
pub latitude: f64,
|
pub latitude: f64,
|
||||||
|
pub url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ struct Db(rocket_db_pools::sqlx::SqlitePool);
|
||||||
#[get("/places")]
|
#[get("/places")]
|
||||||
async fn get_places(mut db: Connection<Db>) -> Result<Json<Vec<Place>>> {
|
async fn get_places(mut db: Connection<Db>) -> Result<Json<Vec<Place>>> {
|
||||||
let places = rocket_db_pools::sqlx::query!(
|
let places = rocket_db_pools::sqlx::query!(
|
||||||
"SELECT id, name, address, open_hours, icon, description," +
|
"SELECT id, name, address, open_hours, icon, description, url," +
|
||||||
r#"longitude as "longitude: f64", latitude as "latitude: f64" FROM places WHERE active = TRUE"#
|
r#"longitude as "longitude: f64", latitude as "latitude: f64" FROM places WHERE active = TRUE"#
|
||||||
)
|
)
|
||||||
.fetch(&mut *db)
|
.fetch(&mut *db)
|
||||||
|
|
@ -29,6 +29,7 @@ async fn get_places(mut db: Connection<Db>) -> Result<Json<Vec<Place>>> {
|
||||||
description: p.description,
|
description: p.description,
|
||||||
latitude: p.latitude,
|
latitude: p.latitude,
|
||||||
longitude: p.longitude,
|
longitude: p.longitude,
|
||||||
|
url: p.url,
|
||||||
})
|
})
|
||||||
.try_collect::<Vec<_>>()
|
.try_collect::<Vec<_>>()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -59,8 +60,8 @@ struct Id {
|
||||||
async fn insert_place(mut db: Connection<Db>, mut place: Json<Place>) -> Result<UpsertResponse> {
|
async fn insert_place(mut db: Connection<Db>, mut place: Json<Place>) -> Result<UpsertResponse> {
|
||||||
let i = ::sqlx::query_as!(
|
let i = ::sqlx::query_as!(
|
||||||
Id,
|
Id,
|
||||||
"INSERT INTO places (name, address, open_hours, icon, description, longitude, latitude)\
|
"INSERT INTO places (name, address, open_hours, icon, description, longitude, latitude, url)\
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)\
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)\
|
||||||
RETURNING id",
|
RETURNING id",
|
||||||
place.name,
|
place.name,
|
||||||
place.address,
|
place.address,
|
||||||
|
|
@ -68,7 +69,8 @@ async fn insert_place(mut db: Connection<Db>, mut place: Json<Place>) -> Result<
|
||||||
place.icon,
|
place.icon,
|
||||||
place.description,
|
place.description,
|
||||||
place.longitude,
|
place.longitude,
|
||||||
place.latitude
|
place.latitude,
|
||||||
|
place.url
|
||||||
)
|
)
|
||||||
.fetch_one(&mut *db)
|
.fetch_one(&mut *db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -79,7 +81,7 @@ async fn insert_place(mut db: Connection<Db>, mut place: Json<Place>) -> Result<
|
||||||
|
|
||||||
async fn update_place(mut db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
async fn update_place(mut db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
||||||
let result = ::sqlx::query!(
|
let result = ::sqlx::query!(
|
||||||
"UPDATE places SET (name, address, open_hours, icon, description, longitude, latitude) = (?, ?, ?, ?, ?, ?, ?) WHERE id = ?",
|
"UPDATE places SET (name, address, open_hours, icon, description, longitude, latitude, url) = (?, ?, ?, ?, ?, ?, ?, ?) WHERE id = ?",
|
||||||
place.name,
|
place.name,
|
||||||
place.address,
|
place.address,
|
||||||
place.open_hours,
|
place.open_hours,
|
||||||
|
|
@ -87,6 +89,7 @@ async fn update_place(mut db: Connection<Db>, place: Json<Place>) -> Result<Upse
|
||||||
place.description,
|
place.description,
|
||||||
place.longitude,
|
place.longitude,
|
||||||
place.latitude,
|
place.latitude,
|
||||||
|
place.url,
|
||||||
place.id,
|
place.id,
|
||||||
)
|
)
|
||||||
.execute(&mut *db)
|
.execute(&mut *db)
|
||||||
|
|
@ -105,7 +108,7 @@ async fn delete_place(mut db: Connection<Db>, id: i64) -> Result<Option<()>> {
|
||||||
.execute(&mut *db)
|
.execute(&mut *db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok((result.rows_affected() == 1).then(|| ()))
|
Ok((result.rows_affected() == 1).then_some(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
|
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,10 @@
|
||||||
<option value="shop">Tienda</option>
|
<option value="shop">Tienda</option>
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="url"> URL:</label>
|
||||||
|
<input type="text" id="url" name="url" size="30">
|
||||||
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<label for="description"> Descripción:</label>
|
<label for="description"> Descripción:</label>
|
||||||
<textarea id="description" name="description"
|
<textarea id="description" name="description"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ interface PlaceModel {
|
||||||
description: string;
|
description: string;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
|
url: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPlaces(): Promise<Array<PlaceModel>> {
|
async function loadPlaces(): Promise<Array<PlaceModel>> {
|
||||||
|
|
@ -24,7 +25,8 @@ function toFeature(place: PlaceModel): Feature {
|
||||||
"address": place.address,
|
"address": place.address,
|
||||||
"open_hours": place.open_hours,
|
"open_hours": place.open_hours,
|
||||||
"icon": place.icon,
|
"icon": place.icon,
|
||||||
"description": place.description
|
"description": place.description,
|
||||||
|
"url": place.url,
|
||||||
},
|
},
|
||||||
"geometry": {
|
"geometry": {
|
||||||
"type": "Point",
|
"type": "Point",
|
||||||
|
|
@ -68,6 +70,8 @@ function getPlaceFromForm(): PlaceModel {
|
||||||
(document.getElementById("lat") as HTMLInputElement).value);
|
(document.getElementById("lat") as HTMLInputElement).value);
|
||||||
const longitude = parseFloat(
|
const longitude = parseFloat(
|
||||||
(document.getElementById("long") as HTMLSelectElement).value);
|
(document.getElementById("long") as HTMLSelectElement).value);
|
||||||
|
const url =
|
||||||
|
(document.getElementById("url") as HTMLInputElement).value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
|
|
@ -78,6 +82,7 @@ function getPlaceFromForm(): PlaceModel {
|
||||||
description: description,
|
description: description,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
|
url: url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,6 +93,7 @@ function clearForm(): void {
|
||||||
const longInput = (document.getElementById("long") as HTMLInputElement);
|
const longInput = (document.getElementById("long") as HTMLInputElement);
|
||||||
const nameInput = (document.getElementById("name") as HTMLInputElement);
|
const nameInput = (document.getElementById("name") as HTMLInputElement);
|
||||||
const addressInput = (document.getElementById("address") as HTMLInputElement);
|
const addressInput = (document.getElementById("address") as HTMLInputElement);
|
||||||
|
const urlInput = (document.getElementById("url") as HTMLInputElement);
|
||||||
const openHoursArea = (document.getElementById("open_hours") as HTMLTextAreaElement);
|
const openHoursArea = (document.getElementById("open_hours") as HTMLTextAreaElement);
|
||||||
const descriptionArea = (document.getElementById("description") as HTMLTextAreaElement);
|
const descriptionArea = (document.getElementById("description") as HTMLTextAreaElement);
|
||||||
|
|
||||||
|
|
@ -97,6 +103,7 @@ function clearForm(): void {
|
||||||
longInput.value = "";
|
longInput.value = "";
|
||||||
nameInput.value = "";
|
nameInput.value = "";
|
||||||
addressInput.value = "";
|
addressInput.value = "";
|
||||||
|
urlInput.value = "";
|
||||||
openHoursArea.value = "";
|
openHoursArea.value = "";
|
||||||
descriptionArea.value = "";
|
descriptionArea.value = "";
|
||||||
|
|
||||||
|
|
@ -168,6 +175,14 @@ async function getAddressReverse(lat: number, long: number): Promise<string> {
|
||||||
return `${address.road} ${address.house_number}, ${address.city}`;
|
return `${address.road} ${address.house_number}, ${address.city}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toLink(url: string): string {
|
||||||
|
let content = url;
|
||||||
|
const m = url.match("https://instagram.com/(.*)");
|
||||||
|
if (m) {
|
||||||
|
content = `@${m[1]}`;
|
||||||
|
}
|
||||||
|
return `<a href="${url}" target="_blank">${content}</a>`
|
||||||
|
}
|
||||||
async function setupMap(): Promise<void> {
|
async function setupMap(): Promise<void> {
|
||||||
/* Create/Edit form */
|
/* Create/Edit form */
|
||||||
const modal = document.getElementById("modal");
|
const modal = document.getElementById("modal");
|
||||||
|
|
@ -204,6 +219,7 @@ async function setupMap(): Promise<void> {
|
||||||
/*Get the form elements*/
|
/*Get the form elements*/
|
||||||
const idInput = (document.getElementById("id") as HTMLInputElement);
|
const idInput = (document.getElementById("id") as HTMLInputElement);
|
||||||
const nameInput = (document.getElementById("name") as HTMLInputElement);
|
const nameInput = (document.getElementById("name") as HTMLInputElement);
|
||||||
|
const urlInput = (document.getElementById("url") as HTMLInputElement);
|
||||||
const openHoursArea = (document.getElementById("open_hours") as HTMLTextAreaElement);
|
const openHoursArea = (document.getElementById("open_hours") as HTMLTextAreaElement);
|
||||||
const iconSelect = (document.getElementById("icon") as HTMLSelectElement);
|
const iconSelect = (document.getElementById("icon") as HTMLSelectElement);
|
||||||
const descriptionArea = (document.getElementById("description") as HTMLTextAreaElement);
|
const descriptionArea = (document.getElementById("description") as HTMLTextAreaElement);
|
||||||
|
|
@ -212,6 +228,7 @@ async function setupMap(): Promise<void> {
|
||||||
idInput.value = place.id.toString();
|
idInput.value = place.id.toString();
|
||||||
nameInput.value = place.name;
|
nameInput.value = place.name;
|
||||||
addressInput.value = place.address;
|
addressInput.value = place.address;
|
||||||
|
urlInput.value = place.url;
|
||||||
openHoursArea.value = place.open_hours;
|
openHoursArea.value = place.open_hours;
|
||||||
iconSelect.value = place.icon;
|
iconSelect.value = place.icon;
|
||||||
descriptionArea.value = place.description;
|
descriptionArea.value = place.description;
|
||||||
|
|
@ -287,19 +304,22 @@ async function setupMap(): Promise<void> {
|
||||||
|
|
||||||
function onEachFeature(feature: Feature, layer: L.Layer) {
|
function onEachFeature(feature: Feature, layer: L.Layer) {
|
||||||
if (feature.properties) {
|
if (feature.properties) {
|
||||||
let popupStr = "<h3>" + feature.properties.name + "</h3>";
|
let popupStr = `<h3>${feature.properties.name}</h3>`;
|
||||||
popupStr += "<ul>"
|
popupStr += "<ul>"
|
||||||
if (feature.properties.address)
|
if (feature.properties.address)
|
||||||
popupStr += "<li><b>Dirección:</b> " + feature.properties.address + "</li>";
|
popupStr += `<li><b>Dirección:</b>${feature.properties.address}</li>`;
|
||||||
if (feature.properties.open_hours)
|
if (feature.properties.open_hours)
|
||||||
popupStr += "<li><b>Horario:</b> " + feature.properties.open_hours + "</li>";
|
popupStr += `<li><b>Horario:</b>${feature.properties.open_hours}</li>`;
|
||||||
if (feature.properties.description)
|
if (feature.properties.description)
|
||||||
popupStr += "<li>" + feature.properties.description + "</li>";
|
popupStr += `<li>${feature.properties.description}</li>`;
|
||||||
|
if (feature.properties.url)
|
||||||
|
popupStr += `<li>${toLink(feature.properties.url)}</li>`;
|
||||||
|
|
||||||
const lnglat = (feature.geometry as Point).coordinates;
|
const lnglat = (feature.geometry as Point).coordinates;
|
||||||
popupStr += "<a href=\"https://www.google.com/maps/dir//" +
|
const lng = lnglat[0];
|
||||||
lnglat[1] + "," + lnglat[0] + "/@" + lnglat[1] + "," + lnglat[0] +
|
const lat = lnglat[0];
|
||||||
",15z\" target=\"_blank\">GMaps</a>"
|
popupStr += `<a href="https://www.google.com/maps/dir//` +
|
||||||
|
`${lat},${lng}/@${lat},${lng},15z" target="_blank">GMaps</a>`
|
||||||
popupStr += "</ul>";
|
popupStr += "</ul>";
|
||||||
|
|
||||||
layer.bindPopup(popupStr);
|
layer.bindPopup(popupStr);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue