diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e97a277 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# used for getting list of sponsors +GITHUB_TOKEN=github_pat_blah-blah-blah diff --git a/.gitignore b/.gitignore index 4e34a40..27a7411 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,5 @@ serverless/*/src/* !serverless/*/src/requirements.txt # TODO: need a better way to handle python packages + +.env diff --git a/docker-compose.yml b/docker-compose.yml index d8435b0..4a9c4c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,3 +12,5 @@ services: retries: 3 # Number of retries before marking as unhealthy start_period: 10s # Time to wait before starting health checks stdin_open: true + environment: + - GITHUB_TOKEN diff --git a/shotgun/build.sbt b/shotgun/build.sbt index 4e249dc..578f94a 100644 --- a/shotgun/build.sbt +++ b/shotgun/build.sbt @@ -14,6 +14,10 @@ lazy val root = (project in file(".")) val PekkoVersion = "1.0.3" val PekkoHttpVersion = "1.0.1" libraryDependencies ++= Seq( + "ch.qos.logback" % "logback-classic" % "1.5.6", + "org.slf4j" % "slf4j-api" % "2.0.12", + "com.auth0" % "jwks-rsa" % "0.22.1", + "com.github.jwt-scala" % "jwt-json-common_native0.4_2.12" % "10.0.1", "org.apache.pekko" %% "pekko-actor-typed" % PekkoVersion, "org.apache.pekko" %% "pekko-stream" % PekkoVersion, "org.apache.pekko" %% "pekko-http" % PekkoHttpVersion, diff --git a/shotgun/src/main/resources/logback.xml b/shotgun/src/main/resources/logback.xml new file mode 100644 index 0000000..e649a23 --- /dev/null +++ b/shotgun/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n + + + + + + + diff --git a/shotgun/src/main/scala/me/deflock/shotgun/ShotgunServer.scala b/shotgun/src/main/scala/me/deflock/shotgun/ShotgunServer.scala index bbbcb9b..92f3043 100644 --- a/shotgun/src/main/scala/me/deflock/shotgun/ShotgunServer.scala +++ b/shotgun/src/main/scala/me/deflock/shotgun/ShotgunServer.scala @@ -17,9 +17,12 @@ import java.net.URLEncoder import java.nio.charset.StandardCharsets import scala.concurrent.ExecutionContextExecutor import scala.io.StdIn +import org.slf4j.LoggerFactory object ShotgunServer { + val logger = LoggerFactory.getLogger(getClass) + def main(args: Array[String]): Unit = { implicit val system: ActorSystem = ActorSystem("my-system") @@ -28,6 +31,7 @@ object ShotgunServer { val client = new services.OverpassClient() val nominatim = new services.NominatimClient() + val githubClient = new services.GithubClient() // CORS val allowedOrigins = List( @@ -72,6 +76,13 @@ object ShotgunServer { } } }, + path("sponsors" / "github") { + get { + onSuccess(githubClient.getSponsors("frillweeman")) { json => + complete(json) + } + } + }, path("oauth2" / "callback") { get { parameters(Symbol("code").?) { (code) => diff --git a/shotgun/src/main/scala/services/GithubClient.scala b/shotgun/src/main/scala/services/GithubClient.scala new file mode 100644 index 0000000..c867d1d --- /dev/null +++ b/shotgun/src/main/scala/services/GithubClient.scala @@ -0,0 +1,68 @@ +package services + +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.http.javadsl.model.headers.{Authorization, HttpCredentials, UserAgent} +import org.apache.pekko.http.scaladsl.Http +import org.apache.pekko.http.scaladsl.model.{HttpMethods, HttpRequest, StatusCodes} +import org.apache.pekko.http.scaladsl.unmarshalling.Unmarshal +import spray.json.JsValue +import spray.json._ +import org.apache.pekko.http.scaladsl.model.ContentTypes +import org.apache.pekko.http.scaladsl.model.HttpEntity +import org.slf4j.LoggerFactory + +import scala.concurrent.{ExecutionContextExecutor, Future} + +class GithubClient(implicit val system: ActorSystem, implicit val executionContext: ExecutionContextExecutor) { + val logger = LoggerFactory.getLogger(getClass) + val graphQLEndpoint = "https://api.github.com/graphql" + private val githubApiToken = sys.env("GITHUB_TOKEN") + + def getSponsors(username: String): Future[JsArray] = { + + val query = s""" + |query { + | user(login: "$username") { + | sponsorshipsAsMaintainer(first: 100) { + | nodes { + | sponsor { + | login + | name + | avatarUrl + | } + | } + | } + | } + |} + |""".stripMargin.replace("\n", " ").replace("\"", "\\\"") + + val jsonRequest = s"""{"query": "$query", "variables": ""}""" + val jsonEntity = HttpEntity(ContentTypes.`application/json`, jsonRequest) + val request = HttpRequest( + headers = List( + UserAgent.create("Shotgun"), + Authorization.create(HttpCredentials.create("Bearer", githubApiToken)) + ), + method = HttpMethods.POST, + uri = graphQLEndpoint, + entity = jsonEntity + ) + + Http().singleRequest(request).flatMap { response => + response.status match { + case StatusCodes.OK => + Unmarshal(response.entity).to[String].map { jsonString => + jsonString.parseJson.asJsObject + .fields("data").asJsObject + .fields("user").asJsObject + .fields("sponsorshipsAsMaintainer") + .asJsObject.fields("nodes") + .asInstanceOf[JsArray] + } + case _ => + response.discardEntityBytes() + Future.failed(new Exception(s"Failed to get sponsors: ${response.status}")) + } + } + } +} diff --git a/shotgun/src/main/scala/services/OverpassClient.scala b/shotgun/src/main/scala/services/OverpassClient.scala index ca27e4f..44f2a92 100644 --- a/shotgun/src/main/scala/services/OverpassClient.scala +++ b/shotgun/src/main/scala/services/OverpassClient.scala @@ -6,7 +6,6 @@ import pekko.http.scaladsl.Http import pekko.http.scaladsl.model._ import pekko.http.scaladsl.unmarshalling.Unmarshal import spray.json._ -import DefaultJsonProtocol._ import scala.concurrent.{ExecutionContextExecutor, Future} case class BoundingBox(minLat: Double, minLng: Double, maxLat: Double, maxLng: Double) diff --git a/webapp/public/torches.webp b/webapp/public/torches.webp new file mode 100644 index 0000000..6bc78db Binary files /dev/null and b/webapp/public/torches.webp differ diff --git a/webapp/src/App.vue b/webapp/src/App.vue index 4b4a3df..1b1a4e8 100644 --- a/webapp/src/App.vue +++ b/webapp/src/App.vue @@ -25,7 +25,7 @@ const metaItems = [ { title: 'Discord', icon: 'mdi-chat-processing-outline', href: 'https://discord.gg/aV7v4R3sKT'}, { title: 'Contact', icon: 'mdi-email-outline', to: '/contact' }, { title: 'GitHub', icon: 'mdi-github', href: 'https://github.com/frillweeman/deflock'}, - { title: 'Donate', icon: 'mdi-heart', href: 'https://github.com/sponsors/frillweeman'}, + { title: 'Donate', icon: 'mdi-heart', to: '/donate'}, ]; const drawer = ref(false) diff --git a/webapp/src/components/DFMapPopup.vue b/webapp/src/components/DFMapPopup.vue index aa98f60..0718f98 100644 --- a/webapp/src/components/DFMapPopup.vue +++ b/webapp/src/components/DFMapPopup.vue @@ -15,7 +15,10 @@ mdi-domain - + + {{ alpr.tags.manufacturer }} + + {{ alpr.tags.brand }} diff --git a/webapp/src/components/OSMTagSelector.vue b/webapp/src/components/OSMTagSelector.vue index 4d1426f..3d009cd 100644 --- a/webapp/src/components/OSMTagSelector.vue +++ b/webapp/src/components/OSMTagSelector.vue @@ -1,15 +1,6 @@