diff --git a/.envrc.local.template b/.envrc.local.template index 1847777c4d2b9d52080f099211e5dfd969bf8dc0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/.envrc.local.template +++ b/.envrc.local.template @@ -1,2 +0,0 @@ -export PLACES_API_KEY="" - diff --git a/README.md b/README.md index f6b1faa46c4511cc9d67988258b512fb0d95114f..f25a4fdfb714dc7ae1112322b97eb0c04e097133 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ The backend for handling donation projects, including a GraphQL API for - [Testing](#testing) - [Tasks](#tasks) - [VS Code extension](#vs-code-extension) - - [Places API](#places-api) - [Local execution](#local-execution) - [Continuous Integration and Deployment](#continuous-integration-and-deployment) - [Skipping parts of CI](#skipping-parts-of-ci) @@ -129,26 +128,6 @@ JetBrains also offers a [plugin](https://plugins.jetbrains.com/plugin/14382-deno) for WebStorm and other JetBrains IDEs. -## Places API - -The server uses Google Places API to create autocomplete predictions for city -names. In order to be able to use the Places API, requests are equipped with an -API key that is used by the Places API to match the project and for billing -purposes. Within the deployment, three keys are in usage (managed by terraform): - -1. `places-api-key-staging` -2. `places-api-key-production` -3. `places-api-key-review` (shared amongst all review environments) - -In addition we deploy another key intended for local development (via -`holi-infra`): - -4. `places-api-key-dev` - -This key is shared amongst all environments. You can receive the key in the -`holi-infra` project value via -`terraform refresh; terraform output --raw places-api-key-dev`. - ## Local execution To watch for file changes during development, run @@ -198,5 +177,3 @@ if "you know what you're doing". | PORT | 8001 | the port to listen on | | CACHE_ENABLED | true | wether or not to enable caching | | CACHE_TTL_MS_BETTERPLACE | 60 seconds | time-to-live in ms for betterplace caching | -| CACHE_TTL_MS_PLACES | 24 hours | time-to-live in ms for places caching | -| PLACES_API_KEY | undefined | Google Maps Places API key | diff --git a/app/main.ts b/app/main.ts index d5aecd5f02c5cee8ec807b5b354cd6a16d8854bf..ea9361b41b1ce48684436422b8212debff3e35e8 100644 --- a/app/main.ts +++ b/app/main.ts @@ -10,8 +10,6 @@ const serverConfigFromEnv = () => { port: asNumber(Deno.env.get("PORT")), cacheEnabled: asBoolean(Deno.env.get("CACHE_ENABLED")), cacheTtlMsBetterplace: asNumber(Deno.env.get("CACHE_TTL_MS_BETTERPLACE")), - cacheTtlMsPlaces: asNumber(Deno.env.get("CACHE_TTL_MS_PLACES")), - placesApiKey: Deno.env.get("PLACES_API_KEY"), }; }; diff --git a/app/places.ts b/app/places.ts deleted file mode 100644 index 99dbfeecfa5b3d847efe95eaeb1c6075fc6efd14..0000000000000000000000000000000000000000 --- a/app/places.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { PlacesApiResults } from "./places_api_types.ts"; -import { Place, PlacesParameters } from "./types.ts"; -import { log } from "./deps.ts"; - -const buildPlacesUrl = ( - { input }: PlacesParameters, - placesApiKey: string, -): URL => { - const url = new URL( - "https://maps.googleapis.com/maps/api/place/autocomplete/json", - ); - url.searchParams.append("input", input); - url.searchParams.append("types", "(cities)"); - url.searchParams.append("key", placesApiKey); - return url; -}; - -export const transformPlacesResult = ( - results: PlacesApiResults, -): Place[] => { - return results.predictions.map((prediction) => ({ - description: prediction.description, - mainText: prediction.structured_formatting.main_text, - mainTextMatchedSubstrings: - prediction.structured_formatting.main_text_matched_substrings, - secondaryText: prediction.structured_formatting.secondary_text, - })); -}; - -export const fetchPlaces = ( - params: PlacesParameters, - placesApiKey: string, -): Promise<Place[]> => { - const url = buildPlacesUrl(params, placesApiKey); - const start = Date.now(); - console.debug(`fetching places for search input ${params.input}`); - return fetch(url) - .then((result) => result.json()) - .then(transformPlacesResult) - .then((result) => { - const duration = Date.now() - start; - log.debug(` fetching places took ${duration} ms`); - return result; - }) - .catch((e) => { - const duration = Date.now() - start; - log.error( - `Error performing request to ${url} after ${duration} ms: ${e.message}`, - ); - throw e; - }); -}; diff --git a/app/places_api_types.ts b/app/places_api_types.ts deleted file mode 100644 index cf2f318adf8af901aee0c68f118fbcfa1b212163..0000000000000000000000000000000000000000 --- a/app/places_api_types.ts +++ /dev/null @@ -1,31 +0,0 @@ -export type PlacesApiSubstringMatch = { - length: number; - offset: number; -}; - -export type PlacesApiStructuredFormatting = { - main_text: string; - main_text_matched_substrings: PlacesApiSubstringMatch[]; - secondary_text: string; -}; - -export type PlacesApiTerm = { - offset: number; - value: string; -}; - -export type PlacesApiType = string; - -export type PlacesApiPrediction = { - description: string; - matched_substrings: PlacesApiSubstringMatch[]; - place_id: string; - reference: string; - structured_formatting: PlacesApiStructuredFormatting; - terms: PlacesApiTerm[]; - types: PlacesApiType[]; -}; - -export type PlacesApiResults = { - predictions: PlacesApiPrediction[]; -}; diff --git a/app/places_test.ts b/app/places_test.ts deleted file mode 100644 index 41dda8237cad89851c69da6105003545997ba0c8..0000000000000000000000000000000000000000 --- a/app/places_test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import setupLogging from "./logging.ts"; -import { - afterEach, - assertEquals, - assertSpyCall, - describe, - it, - Stub, -} from "./dev_deps.ts"; -import { processGqlRequest, stubFetch } from "./common_test.ts"; -import { fetchPlaces } from "./places.ts"; -import { - expectedGqlResponseHam, - placesResponseHam, -} from "./places_test_data.ts"; -import { Place } from "./types.ts"; -import { createGraphQLServer, GraphQLServer } from "./server.ts"; - -const emptyResponse = { - predictions: [], -}; - -const placeFragment = ` - description - mainText - secondaryText - mainTextMatchedSubstrings { - length - offset - } -`; - -const placesQuery = (input: string) => ` -{ - places(input: "${input}") { - ${placeFragment} - } -} -`; - -const queryPlaces = async ( - graphQLServer: GraphQLServer, - query: string, -): Promise<Place[]> => { - return (await processGqlRequest(graphQLServer, query))?.places as Place[]; -}; - -setupLogging("test"); - -describe("places", () => { - let fetchStub: Stub; - - afterEach(() => { - fetchStub?.restore(); - }); - - it("calls api with correct parameters", async () => { - fetchStub = stubFetch(emptyResponse); - - const placesApiKey = "random_string"; - await fetchPlaces({ input: "Ham" }, placesApiKey); - - const expectedUrl = new URL( - `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=Ham&types=%28cities%29&key=${placesApiKey}`, - ); - assertSpyCall(fetchStub, 0, { args: [expectedUrl] }); - }); - - it("correctly parses result and returns", async () => { - fetchStub = stubFetch(placesResponseHam); - const graphQLServer = createGraphQLServer({}); - const places = await queryPlaces(graphQLServer, placesQuery("Ham")); - assertEquals(places, expectedGqlResponseHam); - }); -}); diff --git a/app/places_test_data.ts b/app/places_test_data.ts deleted file mode 100644 index 056beb385777f9d0cad513becf603e7765af2d76..0000000000000000000000000000000000000000 --- a/app/places_test_data.ts +++ /dev/null @@ -1,110 +0,0 @@ -const placesPredictionHamburg = { - "description": "Hamburg, Germany", - "matched_substrings": [ - { - "length": 3, - "offset": 0, - }, - ], - "place_id": "ChIJuRMYfoNhsUcRoDrWe_I9JgQ", - "reference": "ChIJuRMYfoNhsUcRoDrWe_I9JgQ", - "structured_formatting": { - "main_text": "Hamburg", - "main_text_matched_substrings": [ - { - "length": 3, - "offset": 0, - }, - ], - "secondary_text": "Germany", - }, - "terms": [ - { - "offset": 0, - "value": "Hamburg", - }, - { - "offset": 9, - "value": "Germany", - }, - ], - "types": [ - "locality", - "political", - "geocode", - ], -}; - -const placesPredictionHamilton = { - "description": "Hamilton, ON, Canada", - "matched_substrings": [ - { - "length": 3, - "offset": 0, - }, - ], - "place_id": "ChIJj3feJ2yYLIgRIQ7f2Fbuais", - "reference": "ChIJj3feJ2yYLIgRIQ7f2Fbuais", - "structured_formatting": { - "main_text": "Hamilton", - "main_text_matched_substrings": [ - { - "length": 3, - "offset": 0, - }, - ], - "secondary_text": "ON, Canada", - }, - "terms": [ - { - "offset": 0, - "value": "Hamilton", - }, - { - "offset": 10, - "value": "ON", - }, - { - "offset": 14, - "value": "Canada", - }, - ], - "types": [ - "locality", - "political", - "geocode", - ], -}; - -export const placesResponseHam = { - "predictions": [ - placesPredictionHamburg, - placesPredictionHamilton, - ], - "status": "OK", -}; - -export const expectedGqlResponseHam = [ - { - "description": "Hamburg, Germany", - "mainText": "Hamburg", - "secondaryText": "Germany", - "mainTextMatchedSubstrings": [ - { - "length": 3, - "offset": 0, - }, - ], - }, - { - "description": "Hamilton, ON, Canada", - "mainText": "Hamilton", - "secondaryText": "ON, Canada", - "mainTextMatchedSubstrings": [ - { - "length": 3, - "offset": 0, - }, - ], - }, -]; diff --git a/app/server.ts b/app/server.ts index 993e0a6ba0caa02a762a77a7312a319cb6d7dc29..dfc169f5ab3bef467a91de197784df63f169fb34 100644 --- a/app/server.ts +++ b/app/server.ts @@ -1,7 +1,6 @@ import { Initiative, NewsParameters, - PlacesParameters, Project, ProjectParameters, ProjectsParameters, @@ -13,7 +12,6 @@ import { fetchProject, fetchProjects, } from "./betterplace.ts"; -import { fetchPlaces } from "./places.ts"; import { createSchema, createYoga, serve, useResponseCache } from "./deps.ts"; const typeDefs = ` @@ -71,27 +69,14 @@ const typeDefs = ` projects: [Project]! } - type PlaceSubstringMatch { - length: Int! - offset: Int! - } - - type Place { - description: String! - mainText: String! - mainTextMatchedSubstrings: [PlaceSubstringMatch]! - secondaryText: String! - } - type Query { # uses offset-based pagination as described in https://www.apollographql.com/docs/react/pagination/offset-based projects(offset: Int, limit: Int, locale: String, location: String): ProjectsResponse! project(id: Int!): Project! - places(input: String!): [Place]! } `; -const createResolvers = (config: ServerConfig) => ({ +const createResolvers = (_config: ServerConfig) => ({ Query: { projects: ( // deno-lint-ignore no-explicit-any @@ -103,11 +88,6 @@ const createResolvers = (config: ServerConfig) => ({ _parent: any, parameters: ProjectParameters, ) => fetchProject(parameters), - places: ( - // deno-lint-ignore no-explicit-any - _parent: any, - parameters: PlacesParameters, - ) => fetchPlaces(parameters, config.placesApiKey || ""), }, Project: { categories: (args: Project) => fetchCategories(args.id), @@ -122,14 +102,11 @@ const createResolvers = (config: ServerConfig) => ({ export const DEFAULT_PORT = 8001; export const DEFAULT_CACHE_ENABLED = true; export const DEFAULT_CACHE_TTL_MS_BETTERPLACE = 60_000; -export const DEFAULT_CACHE_TTL_MS_PLACES = 60_000 * 60 * 24; export interface ServerConfig { port?: number; // default: 8001 cacheEnabled?: boolean; // default: true cacheTtlMsBetterplace?: number; // default: 60 seconds - cacheTtlMsPlaces?: number; // default: 24 hours - placesApiKey?: string; // must be given in order for places calls to work } export const createGraphQLServer = ( @@ -143,8 +120,6 @@ export const createGraphQLServer = ( DEFAULT_CACHE_TTL_MS_BETTERPLACE, "Query.project": config.cacheTtlMsBetterplace || DEFAULT_CACHE_TTL_MS_BETTERPLACE, - "Query.places": config.cacheTtlMsPlaces || - DEFAULT_CACHE_TTL_MS_PLACES, }, })] : []; diff --git a/app/types.ts b/app/types.ts index a8c1708b4e4a9bf151617798fee19a42da7672fa..72caa0c538bdb95551c9c18051e06b814418f909 100644 --- a/app/types.ts +++ b/app/types.ts @@ -71,19 +71,3 @@ export interface ProjectsResponse { totalResults: number; projects: Project[]; } - -export interface PlaceSubstringMatch { - length: number; - offset: number; -} - -export interface Place { - description: string; - mainText: string; - mainTextMatchedSubstrings: PlaceSubstringMatch[]; - secondaryText: string; -} - -export interface PlacesParameters { - input: string; -} diff --git a/terraform/environments/deployment.tf b/terraform/environments/deployment.tf index d904126d29814a74561713c7ace75d195b2985b4..7e4e671da18a06d5ab04b9ce5a32666ccf68e65a 100644 --- a/terraform/environments/deployment.tf +++ b/terraform/environments/deployment.tf @@ -12,7 +12,6 @@ resource "google_project_service" "service" { for_each = toset([ "apikeys.googleapis.com", "endpoints.googleapis.com", - "places-backend.googleapis.com", "run.googleapis.com", "servicecontrol.googleapis.com", "servicemanagement.googleapis.com", @@ -47,18 +46,6 @@ resource "google_cloud_run_service" "donations_api" { name = "ENVIRONMENT" value = local.environment } - env { - name = "PLACES_API_KEY" - value = google_apikeys_key.deployment.key_string - } - # env { - # name = "ENVIRONMENT_ID" - # value = local.environment_name - # } - # env { - # name = "SENTRY_DSN" - # value = "https://0e0fac0c76884e0a95432d780c4560fc@o949544.ingest.sentry.io/6741291" - # } resources { limits = { @@ -135,17 +122,3 @@ resource "google_cloud_run_service_iam_policy" "donations_api" { policy_data = data.google_iam_policy.donations_api.policy_data } - -resource "google_apikeys_key" "deployment" { - display_name = "places-api-key-${local.environment_name}" - name = "places-api-key-${local.environment_name}" - project = data.terraform_remote_state.holi_infra_state.outputs.shared_project_id - - restrictions { - api_targets { - service = "places-backend.googleapis.com" - } - } - - depends_on = [google_project_service.service] -}