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..57a3edf 100644 --- a/shotgun/build.sbt +++ b/shotgun/build.sbt @@ -14,6 +14,8 @@ 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", "org.apache.pekko" %% "pekko-actor-typed" % PekkoVersion, "org.apache.pekko" %% "pekko-stream" % PekkoVersion, "org.apache.pekko" %% "pekko-http" % PekkoHttpVersion, @@ -21,3 +23,10 @@ libraryDependencies ++= Seq( "org.apache.pekko" %% "pekko-http-cors" % PekkoHttpVersion, "org.apache.pekko" %% "pekko-slf4j" % PekkoVersion, ) + +assembly / assemblyMergeStrategy := { + case PathList("module-info.class") => MergeStrategy.first + case x => + val oldStrategy = (assembly / assemblyMergeStrategy).value + oldStrategy(x) +} 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/index.html b/webapp/index.html index ca14df7..f074ce2 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -6,6 +6,9 @@ + + + diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 6194b56..61dd3fb 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@types/leaflet.markercluster": "^1.5.5", + "@unhead/vue": "^1.11.14", "axios": "^1.7.7", "countup.js": "^2.8.0", "leaflet.markercluster": "^1.5.3", @@ -702,6 +703,59 @@ "undici-types": "~6.19.2" } }, + "node_modules/@unhead/dom": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.14.tgz", + "integrity": "sha512-FaHCWo9JR4h7PCpSRaXuMC6ifXOuBzlI0PD1MmUcxND2ayDl1d6DauIbN8TUf9TDRxNkrK1Ehb0OCXjC1ZJtrg==", + "dependencies": { + "@unhead/schema": "1.11.14", + "@unhead/shared": "1.11.14" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/schema": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.14.tgz", + "integrity": "sha512-V9W9u5tF1/+TiLqxu+Qvh1ShoMDkPEwHoEo4DKdDG6ko7YlbzFfDxV6el9JwCren45U/4Vy/4Xi7j8OH02wsiA==", + "dependencies": { + "hookable": "^5.5.3", + "zhead": "^2.2.4" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/shared": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.14.tgz", + "integrity": "sha512-41Qt4PJKYVrEGOTXgBJLRYrEu3S7n5stoB4TFC6312CIBVedXqg7voHQurn32LVDjpfJftjLa2ggCjpqdqoRDw==", + "dependencies": { + "@unhead/schema": "1.11.14" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/vue": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.14.tgz", + "integrity": "sha512-6nfi7FsZ936gscmj+1nUB1pybiFMFbnuEFo7B/OY2klpLWsYDUOVvpsJhbu7C3u7wkTlJXglmAk6jdd8I7WgZA==", + "dependencies": { + "@unhead/schema": "1.11.14", + "@unhead/shared": "1.11.14", + "defu": "^6.1.4", + "hookable": "^5.5.3", + "unhead": "1.11.14" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=2.7 || >=3" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", @@ -966,6 +1020,11 @@ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1083,6 +1142,11 @@ "he": "bin/he" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1421,6 +1485,20 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, + "node_modules/unhead": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.14.tgz", + "integrity": "sha512-XmXW0aZyX9kGk9ejCKCSvv/J4T3Rt4hoAe2EofM+nhG+zwZ7AArUMK/0F/fj6FTkfgY0u0/JryE00qUDULgygA==", + "dependencies": { + "@unhead/dom": "1.11.14", + "@unhead/schema": "1.11.14", + "@unhead/shared": "1.11.14", + "hookable": "^5.5.3" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, "node_modules/vite": { "version": "5.4.7", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", @@ -1605,6 +1683,14 @@ "engines": { "node": ">= 8" } + }, + "node_modules/zhead": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz", + "integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==", + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } } }, "dependencies": { @@ -1961,6 +2047,44 @@ "undici-types": "~6.19.2" } }, + "@unhead/dom": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.14.tgz", + "integrity": "sha512-FaHCWo9JR4h7PCpSRaXuMC6ifXOuBzlI0PD1MmUcxND2ayDl1d6DauIbN8TUf9TDRxNkrK1Ehb0OCXjC1ZJtrg==", + "requires": { + "@unhead/schema": "1.11.14", + "@unhead/shared": "1.11.14" + } + }, + "@unhead/schema": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.14.tgz", + "integrity": "sha512-V9W9u5tF1/+TiLqxu+Qvh1ShoMDkPEwHoEo4DKdDG6ko7YlbzFfDxV6el9JwCren45U/4Vy/4Xi7j8OH02wsiA==", + "requires": { + "hookable": "^5.5.3", + "zhead": "^2.2.4" + } + }, + "@unhead/shared": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.14.tgz", + "integrity": "sha512-41Qt4PJKYVrEGOTXgBJLRYrEu3S7n5stoB4TFC6312CIBVedXqg7voHQurn32LVDjpfJftjLa2ggCjpqdqoRDw==", + "requires": { + "@unhead/schema": "1.11.14" + } + }, + "@unhead/vue": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.14.tgz", + "integrity": "sha512-6nfi7FsZ936gscmj+1nUB1pybiFMFbnuEFo7B/OY2klpLWsYDUOVvpsJhbu7C3u7wkTlJXglmAk6jdd8I7WgZA==", + "requires": { + "@unhead/schema": "1.11.14", + "@unhead/shared": "1.11.14", + "defu": "^6.1.4", + "hookable": "^5.5.3", + "unhead": "1.11.14" + } + }, "@vitejs/plugin-vue": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", @@ -2196,6 +2320,11 @@ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true }, + "defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2270,6 +2399,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2488,6 +2622,17 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, + "unhead": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.14.tgz", + "integrity": "sha512-XmXW0aZyX9kGk9ejCKCSvv/J4T3Rt4hoAe2EofM+nhG+zwZ7AArUMK/0F/fj6FTkfgY0u0/JryE00qUDULgygA==", + "requires": { + "@unhead/dom": "1.11.14", + "@unhead/schema": "1.11.14", + "@unhead/shared": "1.11.14", + "hookable": "^5.5.3" + } + }, "vite": { "version": "5.4.7", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", @@ -2557,6 +2702,11 @@ "requires": { "isexe": "^2.0.0" } + }, + "zhead": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz", + "integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==" } } } diff --git a/webapp/package.json b/webapp/package.json index eb9738f..469f372 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -11,6 +11,7 @@ "type-check": "vue-tsc --build --force" }, "dependencies": { + "@unhead/vue": "^1.11.14", "@types/leaflet.markercluster": "^1.5.5", "axios": "^1.7.7", "countup.js": "^2.8.0", diff --git a/webapp/public/chicago-pd.jpg b/webapp/public/chicago-pd.jpg new file mode 100644 index 0000000..69de0de Binary files /dev/null and b/webapp/public/chicago-pd.jpg differ 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..581de5c 100644 --- a/webapp/src/App.vue +++ b/webapp/src/App.vue @@ -15,6 +15,7 @@ const items = [ { title: 'Home', icon: 'mdi-home', to: '/' }, { title: 'Map', icon: 'mdi-map', to: '/map' }, { title: 'What is an ALPR?', icon: 'mdi-cctv', to: '/what-is-an-alpr' }, + { title: 'Dangers of ALPRs', icon: 'mdi-shield-alert', to: '/dangers' }, { title: 'Report an ALPR', icon: 'mdi-map-marker-plus', to: '/report' }, { title: 'Known Operators', icon: 'mdi-police-badge', to: '/operators' }, // { title: 'About', icon: 'mdi-information', to: '/about' }, @@ -25,7 +26,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/assets/main.css b/webapp/src/assets/main.css index 1f425f4..1ccee5e 100644 --- a/webapp/src/assets/main.css +++ b/webapp/src/assets/main.css @@ -21,3 +21,12 @@ a:hover { .leaflet-bar a { color: var(--df-text-color) !important; } + +p { + font-family: "PT Serif", serif; + /* font-size: 1.1em; */ +} + +.serif { + font-family: "PT Serif", serif; +} diff --git a/webapp/src/components/DFMapPopup.vue b/webapp/src/components/DFMapPopup.vue index 478e5d8..413dc9e 100644 --- a/webapp/src/components/DFMapPopup.vue +++ b/webapp/src/components/DFMapPopup.vue @@ -15,7 +15,10 @@ mdi-factory - + + {{ alpr.tags.manufacturer }} + + {{ alpr.tags.brand }} diff --git a/webapp/src/components/Dangers.vue b/webapp/src/components/Dangers.vue new file mode 100644 index 0000000..afa6a3c --- /dev/null +++ b/webapp/src/components/Dangers.vue @@ -0,0 +1,178 @@ + + + + + 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 @@