Mainpage
404 with Link suggestions
This application is deployed at https://go.d1m.dev. Signup is enabled for view only mode.
This is an implementation of Go Links powered by Next.js, GraphQL through PostGraphile and Auth0.
In short, Go Links are a type of URL Shorteners. You can create an alias that points to an URL and will redirect the user to that URL.
Please check the Related section to have a glance on how other companies and universities leverage go links.
- golinks.ncsu.edu
- go.middlebury.edu
- brown.edu/go
- github.com/kellegous/go
- The quick and simple guide to go links
- Google's go link culture
- Open Source: github/kellegous/go
- Open Source/Freemium: trot.to
- Freemium: goatcodes.com
- Freemium: golinks.io
These are just a few ideas that come in my mind. Please feel free to suggest more features by creating an Issue. I'd love to hear your thoughts.
Contributions for the following are very welcome.
- Create Links
- Delete Links
- Edit Links
- Redirect Links
- Auth
- Can be disabled
- Powered by Auth0
- Security
- Row Level Security using Auth0 Roles and Permissions
- Link Description
- Link Suggestion on 404
- Link Usage Metrics
- Number: Usage Total Count
- Graph: Usage of last 31 days
- Link Ownership
- Link Parameters
- For example, a
gh
alias with urlhttps://github.com/$1/$2
allowshttps://go/gh/armand1m/golinks
to be possible.
- For example, a
- Link Groups (Folders)
- URL: Accept
/
and can be redirected - UI: Folds URL groups
- URL: Accept
- Private Links
- Temporary Links
- Random Alias
- Help section
- Chrome Plugin
Aliases created can be accessed through your deployment URL + the alias name. (e.g.: https://go.d1m.dev/twitter redirects to my twitter)
This allows Chrome to recognize the "go" keyword in the address bar. Type "go", a space and then the alias for your link.
- Go to chrome://settings/searchEngines > Other search engines > Add
- Search engine: golinks
- Keyword: go
- URL with
%s
in place of query: https://go.mydomain.com/%s
A Docker Image is available at Docker Hub: https://hub.docker.com/r/armand1m/golinks
NOTE: This application used to be deployed on GKE with a self-hosted postgres instance. Over time, I've had many issues with both cost of this setup, and the degraded performance. As of 2023, this application is now hosted on https://fly.io, which offered a much simpler and cheaper option to run this application while also being faster and easier to maintain. That said, I kept the kubernetes manifests for future reference, or in case I end up coming back to kubernetes for side projects for any reason :)
Make sure to change the manifests accordingly to your environment.
Check the ./kubernetes
folder for k8s manifests content.
These manifests deploy the application together with a cloud_sql_proxy
sidecar to allow networking with Google Cloud SQL.
Create a secret to keep the connection string:
kubectl create secret generic golinks-database \
--from-literal=connectionstring='postgres://<user>:<pass>@<host>:5432/golinks'
Create a secret to keep the Cloud SQL service account:
kubectl create secret generic cloudsql-service-account \
--from-file=service-account.json=./service-account.json
Create a secret to keep Auth0 ids and secrets:
kubectl create secret generic auth0-properties \
--from-literal=client_id='auth0-app-client-id' \
--from-literal=client_secret='auth0-app-client-secret' \
--from-literal=cookie_secret='random-cookie-secret'
Export needed environment variables for envsubst
:
export GOOGLE_CLOUD_PROJECT=<gcp-project>
export GOOGLE_CLOUD_REGION=<gcp-region>
export CLOUDSQL_INSTANCE_NAME=<cloud-sql-instance-name>
export HOSTNAME=go.mydomain.com
export PROTO=https
export LOGONAME=golinks
export AUTH0_ENABLED=true
export AUTH0_DOMAIN=<auth0-domain>
export AUTH0_AUDIENCE=<auth0-audience>
export AUTH0_COOKIE_DOMAIN=go.mydomain.com
export AUTH0_REDIRECT_URL=https://go.mydomain.com/api/callback
export AUTH0_POST_LOGOUT_REDIRECT_URL=https://go.mydomain.com
Create a deployment and service:
cat ./kubernetes/deployment.yaml | envsubst | kubectl apply -f -
kubectl apply -f ./kubernetes/service.yaml
Make sure to change the manifests accordingly to your environment.
Create the virtual service and destination rules:
# switch for the name of your gateway
export ISTIO_GATEWAY_NAME=istio-ingressgateway
export HOSTNAME=go.mydomain.com
cat ./kubernetes/istio/virtual-service.yaml | envsubst | kubectl apply -f -
kubectl apply -f ./kubernetes/istio/destination-rule.yaml
This app leverages Auth0 as an Identity provider. Auth0 is used to manage users and their permissions to access and modify data in this application.
To enable, make sure you set the AUTH0_ENABLED
env var as true
.
In case this is set to false
, every other environment variable prefixed with AUTH0_
can be considered optional.
In the future, these steps will be automated through the Auth0 Provider for Terraform.
Create a Regular Web Application:
It's important that it is a Regular Web Application since this is a Next.js app. It also relies on the accessToken
being a JWT token, so the server can extract roles and permissions from Auth0.
Setup callback and logout urls:
Setup the callback and logout url's to redirect to your domain + the route.
E.g.:
Callback URL: http://localhost:3000/api/callback
Post Logout Redirect URL: http://localhost:3000
Keep the audience
, domain
, client_id
and client_secret
for easy access, as you'll need these to spin up the server (both in development and production)
Create the following roles and permissions:
I'm using YAML here to give a better representation of how the permissions should be setup in Auth0 roles:
role: editor
permissions:
- create:golinks
- update:golinks
- delete:golinks
role: viewer
permissions:
- read:golinks
These roles are used by Postgraphile when setting up a transaction for a query in a specific request context. This allows us to leverage Row Level Security through Postgres Policies to avoid access to data in the source of truth.
These roles are also used in the frontend to avoid rendering features for the user.
Create an user and assign roles:
Create an user and assign both the editor
and viewer
roles so you have access to all features.
armand1m/golinks
is a Next.js app using GraphQL.
The database must be a Postgres 12.x database as the GraphQL API is generated using Postgraphile and leverages features like Row Level Security only available from Postgres 9.6+.
PostGraphile is then used as a NPM module and served through Next.js routes itself, so you don't have to worry about CORS, and the API is initialized together with the Next.js application.
GraphQL Type definitions are generated on application startup during development, so make sure your database executed the initialization scripts during startup as PostGraphile will infer them to the generate the type-defs.graphqls
file. (This brings some caveats when making breaking changes in the database schema during development time, but easy to overcome.)
graphql-let
then is used to generate type definitions in Typescript for development use.
For development, we use the official postgres
docker image. Migrations need to be ran manually using dbmate
and the SQL scripts provided.
Start the database:
docker-compose up -d db
Run the migrations using dbmate
:
export DATABASE_URL=postgres://dev:dev@127.0.0.1:5432/golinks?sslmode=disable
dbmate up
Regenerate the ./lib/type-defs.graphqls
with:
npx postgraphile \
--connection 'postgres://dev:dev@127.0.0.1:5432/golinks' \
--schema public \
--export-schema-graphql ./lib/type-defs.graphqls \
--subscriptions \
--dynamic-json \
--no-setof-functions-contain-nulls \
--no-ignore-rbac \
--no-ignore-indexes \
--show-error-stack=json \
--extended-errors hint,detail,errcode \
--append-plugins @graphile-contrib/pg-simplify-inflector \
--enable-query-batching \
--legacy-relations omit \
--no-server
Create an .env.local
file (with auth0 disabled):
cat > ./.env.local <<EOL
DATABASE_CONNECTION_STRING=postgres://dev:dev@127.0.0.1:5432/golinks
DATABASE_SCHEMA=public
NODE_ENV=development
AUTH0_ENABLED=false
PROTO=http
HOSTNAME=localhost:3000
LOGONAME=go.localhost
EOL
Download dependencies and run in development mode:
yarn
yarn dev
Access http://localhost:3000 and you should have a live development environment running.
cat > ./.env.local <<EOL
DATABASE_CONNECTION_STRING=postgres://dev:dev@db:5432/golinks
DATABASE_SCHEMA=public
NODE_ENV=production
AUTH0_ENABLED=true
AUTH0_DOMAIN=<auth0-domain>
AUTH0_AUDIENCE=<auth0-audience>
AUTH0_CLIENT_ID=<auth0-client-id>
AUTH0_CLIENT_SECRET=<auth0-client-secret>
AUTH0_COOKIE_SECRET=<auth0-cookie-secret>
AUTH0_COOKIE_DOMAIN=localhost
AUTH0_REDIRECT_URL=http://localhost:3000/api/callback
AUTH0_POST_LOGOUT_REDIRECT_URL=http://localhost:3000
HOSTNAME=localhost:3000
PROTO=http
LOGONAME=go.mydomain.dev
EOL
docker-compose up
Access http://localhost:3000
# Environment Variables for the Application
cat > ./.env.cloud <<EOL
DATABASE_CONNECTION_STRING=postgres://<postgraphile-user>:<postgraphile-user-password>@db:5432/golinks
DATABASE_SCHEMA=public
NODE_ENV=production
AUTH0_ENABLED=true
AUTH0_DOMAIN=<auth0-domain>
AUTH0_AUDIENCE=<auth0-audience>
AUTH0_CLIENT_ID=<auth0-client-id>
AUTH0_CLIENT_SECRET=<auth0-client-secret>
AUTH0_COOKIE_SECRET=<auth0-cookie-secret>
AUTH0_COOKIE_DOMAIN=localhost
AUTH0_REDIRECT_URL=http://localhost:3000/api/callback
AUTH0_POST_LOGOUT_REDIRECT_URL=http://localhost:3000
HOSTNAME=localhost:3000
PROTO=http
LOGONAME=go.mydomain.dev
EOL
# Environment Variables for the Cloud SQL Proxy
export GCP_KEY_PATH="~/cloud-sql-service-account.json"
export CLOUDSQL_INSTANCE="<gcp-project>:<gcp-region>:<cloud-sql-instance-name>=tcp:0.0.0.0:5432"
docker-compose -f ./docker-compose-cloud-sql.yml up
./clean-local-database.sh
docker build . -t armand1m/golinks
MIT © Armando Magalhaes