Skip to content
This repository has been archived by the owner on Sep 22, 2023. It is now read-only.

Commit

Permalink
seanmcmamen (#117)
Browse files Browse the repository at this point in the history
* backend implementation and UT (-integration test @ DinerRepo)

* change table name

* refactor rename

* add IT

* add gmaps column

* stash

* alter table and add City enum

* fix compile

* fix test:compile

* fix UT

* add City enum-1

* backend finished

* init FE

* backend selesai

* integrate FE and BE

* add current location

* fix and add UT

* coverage-off
  • Loading branch information
bayusuryadana authored Aug 16, 2023
1 parent 728627d commit e1fca0d
Show file tree
Hide file tree
Showing 25 changed files with 667 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ project
/.settings
docker/storage/.minio.sys
/RUNNING_PID
/.bloop
/.metals
/.vscode
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## Feature
- API: telegram webhook
- Web: Dota & Wallet app
- Web: Mamen, Dota, Wallet app
- Scheduler: Birthday, DotaMetadata, News, (broken: Instagram, Instastory, Twitter)

## Prerequisites
Expand Down
69 changes: 68 additions & 1 deletion docker/db/backup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
--

-- Dumped from database version 11.4
-- Dumped by pg_dump version 14.2
-- Dumped by pg_dump version 14.6 (Homebrew)

SET statement_timeout = 0;
SET lock_timeout = 0;
Expand Down Expand Up @@ -153,6 +153,47 @@ CREATE TABLE public.players (

ALTER TABLE public.players OWNER TO postgres;

--
-- Name: stalls; Type: TABLE; Schema: public; Owner: postgres
--

CREATE TABLE public.stalls (
id integer NOT NULL,
name character varying(100) NOT NULL,
description character varying(1000),
address character varying(100) NOT NULL,
city_id integer NOT NULL,
latitude double precision NOT NULL,
longitude double precision NOT NULL,
youtube_url character varying(2048) NOT NULL,
gmaps_url character varying(2048) NOT NULL
);


ALTER TABLE public.stalls OWNER TO postgres;

--
-- Name: stalls_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--

CREATE SEQUENCE public.stalls_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;


ALTER TABLE public.stalls_id_seq OWNER TO postgres;

--
-- Name: stalls_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres
--

ALTER SEQUENCE public.stalls_id_seq OWNED BY public.stalls.id;


--
-- Name: stocks; Type: TABLE; Schema: public; Owner: postgres
--
Expand Down Expand Up @@ -208,6 +249,13 @@ ALTER TABLE public.wallets_id_seq OWNER TO postgres;
ALTER SEQUENCE public.wallets_id_seq OWNED BY public.wallets.id;


--
-- Name: stalls id; Type: DEFAULT; Schema: public; Owner: postgres
--

ALTER TABLE ONLY public.stalls ALTER COLUMN id SET DEFAULT nextval('public.stalls_id_seq'::regclass);


--
-- Name: wallets id; Type: DEFAULT; Schema: public; Owner: postgres
--
Expand Down Expand Up @@ -592,6 +640,18 @@ COPY public.players (id, realname, avatarfull, personaname, rank_tier) FROM stdi
\.
--
-- Data for Name: stalls; Type: TABLE DATA; Schema: public; Owner: postgres
--
COPY public.stalls (id, name, description, address, city_id, latitude, longitude, youtube_url, gmaps_url) FROM stdin;
1 Ayam Suharti Rumah makan jual ayam goreng kremes Jl. Kapten tendean No.23, Jakarta Selatan 1 -6.23000000000000043 106.670000000000002 https://youtube.com https://goo.gl/maps/dxcXghzHrhn96AZy6
2 Rm. Sederhana Masakan bundo ajo Jl. Balai Pustaka No.1, Jakarta Timur 1 -6.23000000000000043 106.670000000000002 https://youtube.com https://goo.gl/maps/dxcXghzHrhn96AZy6
3 Warteg Bahari Masakan mahasiswa sehari-hari Jl. Desa Putra No.5 Jakarta Barat 2 -8.23000000000000043 110.670000000000002 https://youtube.com https://goo.gl/maps/dxcXghzHrhn96AZy6
4 Sate Madura Pak Kumis Gue punya ayam nih. Bakar rumah lu yuk Jl. Mendoan selatan No.46 Jakarta Utara 2 -8.23000000000000043 110.670000000000002 https://youtube.com https://goo.gl/maps/dxcXghzHrhn96AZy6
\.
--
-- Data for Name: stocks; Type: TABLE DATA; Schema: public; Owner: postgres
--
Expand All @@ -614,6 +674,13 @@ COPY public.wallets (id, date, name, category, currency, amount, done, account)
\.
--
-- Name: stalls_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
--
SELECT pg_catalog.setval('public.stalls_id_seq', 1, false);
--
-- Name: wallets_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
--
Expand Down
11 changes: 11 additions & 0 deletions docker/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,14 @@ CREATE TABLE caches (
value character varying(40000) NOT NULL,
expiry integer
);

CREATE TABLE stalls (
id SERIAL,
name character varying(100) NOT NULL,
plus_code character varying(1000) NOT NULL,
city_id integer NOT NULL,
youtube_url character varying(2048) NOT NULL,
gmaps_url character varying(2048) NOT NULL,
latitude float,
longitude float
);
17 changes: 17 additions & 0 deletions src/it/scala/com/seanmcapp/repository/StallRepoSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.seanmcapp.repository

import com.seanmcapp.repository.seanmcmamen.StallRepoImpl
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec

class StallRepoSpec extends AsyncWordSpec with Matchers {

// "should return all stalls" in {
// val response = StallRepoImpl.getAll
// response.map { res =>
// res.size shouldEqual 4
// res.map(_.name) shouldEqual List("Ayam Suharti", "Rm. Sederhana", "Warteg Bahari", "Sate Madura Pak Kumis")
// }
// }

}
4 changes: 3 additions & 1 deletion src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ twitter {
token = ${?TWITTER_ACCESS_TOKEN}
secret = ${?TWITTER_ACCESS_SECRET}
}
}
}

google.key = ${?GOOGLE_KEY}
1 change: 1 addition & 0 deletions src/main/resources/assets/images/gmaps.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/main/resources/assets/images/youtube.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 106 additions & 0 deletions src/main/resources/assets/javascripts/mamen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
window.initMap = initMap;

let map;
var openedInfoWindow = null;

function initMap() {
// Default to Jakarta
const defaultLatLng = {
lat: -6.172018,
lng: 106.801848
};

map = new google.maps.Map(document.getElementById("map"), {
zoom: 13,
center: defaultLatLng,
});

renderMap();
}

function renderMap() {
google.maps.event.addListener(map, "click", function(event) {
if (openedInfoWindow) openedInfoWindow.close()
});

google.maps.event.addListener(map, "idle", function() {
const nw = {
lat: map.getBounds().getNorthEast().lat(),
lng: map.getBounds().getNorthEast().lng()
}
const se = {
lat: map.getBounds().getSouthWest().lat(),
lng: map.getBounds().getSouthWest().lng()
}

fetchStalls(nw, se);
});
}

async function fetchStalls(nw, se) {
fetch('/api/mamen', {
method: 'POST',
headers: {
'Accept': '*/*',
},
body: JSON.stringify({filter: {geo: {nw: nw, se: se}}}),
})
.then(res => res.json())
.then(stalls => {
placeMarkersAndInfoWindows(stalls)
});
}

function placeMarkersAndInfoWindows(stalls) {
stalls.forEach( function(stall) {
const marker = new google.maps.Marker({
position: {
lat: stall.latitude,
lng: stall.longitude
},
map,
});

const contentString =
'<div class="cell-12">' +
'<p><b>'+stall.name+'</b></p>' +
'<div class="row" style="text-align: center">' +
'<div class="cell-6">' +
'<a class="button" href="'+stall.gmapsUrl+'" role="button square" style="background-color: transparent">' +
'<img src="/assets/images/gmaps.svg"></a></div>' +
'<div class="cell-6">' +
'<a class="button" href="'+stall.youtubeUrl+'" role="button square" style="background-color: transparent">' +
'<img src="/assets/images/youtube.svg"></a></div>' +
'</div>' +
'</div>'

marker.addListener("click", () => {
if (openedInfoWindow) {
openedInfoWindow.close();
}
const infoWindow = new google.maps.InfoWindow({
content: contentString,
});
openedInfoWindow = infoWindow;
infoWindow.open({
anchor: marker,
map
});
});
});
}

function initGeolocation() {
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition( (position) => {
map.setCenter({
lat: position.coords.latitude,
lng: position.coords.longitude
});
}, () => {
alert('Failed to get your location.')
});
} else {
alert('Geolocation is not available.')
}
}
15 changes: 15 additions & 0 deletions src/main/resources/assets/stylesheets/mamen.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#map {
height: 100%;
}

body, html {
height: 100%;
}

#map-container {
height: 100%;
}

#map-row {
height: 80%;
}
7 changes: 7 additions & 0 deletions src/main/scala/com/seanmcapp/Configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,10 @@ object TwitterConf extends Configuration[TwitterConf]("twitter") {
)
}
}

case class GoogleConf(key: String)
object GoogleConf extends Configuration[GoogleConf]("google") {
override def buildConfig(c: Config): GoogleConf = {
GoogleConf(Try(c.getString("key")).getOrElse(""))
}
}
5 changes: 5 additions & 0 deletions src/main/scala/com/seanmcapp/Injection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.seanmcapp.repository.{CacheRepo, CacheRepoImpl, FileRepo, FileRepoImp
import com.seanmcapp.repository.birthday.{PeopleRepo, PeopleRepoImpl}
import com.seanmcapp.repository.dota._
import com.seanmcapp.repository.instagram._
import com.seanmcapp.repository.seanmcmamen.{StallRepo, StallRepoImpl}
import com.seanmcapp.repository.seanmcwallet.{WalletRepo, WalletRepoImpl}
import com.seanmcapp.service._

Expand All @@ -21,6 +22,7 @@ trait Injection {
val walletRepo: WalletRepo = WalletRepoImpl
val cacheRepo: CacheRepo = CacheRepoImpl
val accountRepo: AccountRepo = AccountRepoImpl
val stallRepo: StallRepo = StallRepoImpl

val httpClient: HttpRequestClient = HttpRequestClientImpl
val telegramClient = new TelegramClient(httpClient)
Expand All @@ -32,6 +34,9 @@ trait Injection {

val cbcClient = new CBCClient(httpClient)
val cbcService = new CBCService(photoRepo, customerRepo, fileRepo, accountRepo, cbcClient, instagramClient)

val googleClient = new GoogleClient(httpClient)
val mamenService = new MamenService(stallRepo, googleClient)

val dotaClient = new DotaClient(httpClient)
val dotaService = new DotaService(playerRepo, heroRepo, heroAttributeRepo, dotaClient)
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/seanmcapp/JsonSerde.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.seanmcapp


import com.seanmcapp.util.ExceptionHandler
import io.circe.{Decoder, Json, Printer}
import io.circe.{Decoder, Json, Printer, parser}
import io.circe.generic.AutoDerivation
import io.circe.parser

// $COVERAGE-OFF$
package object external extends AutoDerivation {
Expand Down
10 changes: 8 additions & 2 deletions src/main/scala/com/seanmcapp/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ class Setup(implicit system: ActorSystem, ec: ExecutionContext) extends Directiv
// get(path( "api" / "instagram" / "special" / Remaining) { session =>
// val sessionOpt = if (session == "null") None else Some(session)
// complete(stalkerService.fetchPosts(AccountGroupTypes.StalkerSpecial, ChatIdTypes.Personal, sessionOpt).map(_.asJson.encode))
// }),
//get(path( "api" / "tweet" )(complete(twitterService.run.map(_.asJson.encode)))),
// }),
// get(path( "api" / "tweet" )(complete(twitterService.run.map(_.asJson.encode)))),
post((path( "api" / "mamen") & entity(as[String])) { request =>
complete(mamenService.search(decode[MamenRequest](request)).map(_.asJson.encode))
}),
get(path( "api" / "mamen" )(complete(mamenService.fetch().map(_.asJson.encode)))),
get(path( "api" / "metadota" )(complete(dotaService.run.map(_.asJson.encode)))),
get(path("api" / "news")(complete(newsService.process(ChatIdTypes.Personal).asJson.encode))),
/////////// WEB ///////////
Expand Down Expand Up @@ -91,6 +95,8 @@ class Setup(implicit system: ActorSystem, ec: ExecutionContext) extends Directiv
}
}
},

get(pathPrefix("mamen")(complete(HttpEntity(utf8, com.seanmcapp.mamen.html.home().body)))),

(get & pathPrefix("assets" / Remaining)){ resourcePath =>
getFromResource(s"assets/$resourcePath")
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/com/seanmcapp/external/GenericModel.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.seanmcapp.external

case class LatLng(lat: Double, lng: Double)
22 changes: 22 additions & 0 deletions src/main/scala/com/seanmcapp/external/GoogleClient.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.seanmcapp.external

import com.seanmcapp.GoogleConf

import java.net.URLEncoder

case class GeocodeGeometry(location: LatLng)
case class GeocodeResult(geometry: GeocodeGeometry)
case class GeocodeResponse(results: List[GeocodeResult])
class GoogleClient(httpClient: HttpRequestClient) {

private val apiKey = GoogleConf.apply().key

def fetchLatLng(plusCode: String): (Option[Double], Option[Double]) = {
val url = s"https://maps.googleapis.com/maps/api/geocode/json?key=$apiKey&address=${URLEncoder.encode(plusCode, "UTF-8")}"
val response = httpClient.sendGetRequest(url)
decode[GeocodeResponse](response).results.headOption.map { geo =>
(geo.geometry.location.lat, geo.geometry.location.lng)
}.unzip
}

}
5 changes: 5 additions & 0 deletions src/main/scala/com/seanmcapp/external/MamenModel.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.seanmcapp.external

case class GeoFilter(nw: LatLng, se: LatLng)
case class MamenFilter(name: Option[String] = None, cityId: Option[Int] = None, geo: Option[GeoFilter] = None)
case class MamenRequest(filter: MamenFilter)
Loading

0 comments on commit e1fca0d

Please sign in to comment.