Add optional URL field (#14)

Co-authored-by: Felipe Contreras Salinas <felipe@bstr.cl>
Reviewed-on: #14
This commit is contained in:
Felipe 2023-03-28 02:01:09 -03:00
parent aabebfd1c4
commit 2279026a81
Signed by: Ludwig
GPG key ID: 441A26F83D31FAFF
6 changed files with 44 additions and 15 deletions

2
Cargo.lock generated
View file

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

View file

@ -0,0 +1 @@
ALTER TABLE places ADD url VARCHAR DEFAULT null;

View file

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

View file

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

View file

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

View file

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