TL;DR Share articles and comments via different social media platforms.
Some while ago I was heavily using services like buffer, zapier and ifttt to automatically share interesting articles on social media. All sharing services had great functionalities (e.g. automated workflows) but you’re always limited in the number of shares you can distribute within a time frame without paying for the premium account. At the same time they all lacked support for sharing via LinkedIn. I had a brief look at the LinkedIn API documentation and decided I’ll implement my own service using Golang for the backend.
Here is the full DEMO.
- Architecture
- Serverless
- Currently I use netlify.com to serve my Golang binary as a netlify build function.
- Hexagonal Architecture (also known as Ports & Adapters)
- I try to encapsulate business logic in its own domain and have clear boundaries between my components (also see Architecture)
- I started working on
gocial
long before I have released my presentation on Hexagonal Architecture (in Python).
- Serverless
- Backend
- Obvisouly, I use Golang as a core language 😎
- I use the echo web framework for implementing most of the HTTP stuff (server, REST API, OAuth workflows)
- for the OAuth part I’ve mainly used goth to do the authentication via the identity providers
- I use stateless authentication and no authentication data (like access tokens) is stored server-side
- I use JWT tokens for authorization and secure, httpOnly cookies as storage mechanism
- I don’t use localStorage nor sessionStorage since in the case of XSS, an attacker could easily access the tokens.
- Frontend
- Initially I’ve implemented the frontend in Vue.js but I switched over to
- Golang HTML templates (server side rendering) and
- TailwindCSS (for styling) and
- Alpine.js (modern jQuery)
- syntax very similar to Vue.js
- also very powerful without the
npm
headaches
- I really like the TailwindCSS and Alpine.js combo as it’s quite minimalistic and feature-rich at the same time
- I do plan to migrate to Vue.js in the future
- Initially I’ve implemented the frontend in Vue.js but I switched over to
- everything related to the business case
- user wants to allow gocial to make posts and his/her behalf
- user can share articles/comments to multiple social media platforms
- contains
- Entities
- Different other domains related to the business case
- each one might contain
- Services
- Repositories/Interfaces
- each one might contain
An identity is something you get after successful authentication. After allowing gocial to interact with Twitter/LinkedIn this struct will be used to hold information about an identity provider:
type IdentityProvider struct {
Provider string `yaml:"provider"`
UserName string `yaml:"name"`
UserID string `yaml:"id"`
UserDescription string `yaml:"description"`
UserAvatarURL string `yaml:"userAvatarURL"`
AccessToken string `yaml:"accessToken"`
AccessTokenSecret string `yaml:"accessTokenSecret"`
RefreshToken string `yaml:"refreshToken"`
ExpiresAt *time.Time `yaml:"expiry"`
}
The oauth package uses goth to implement the OAuth workflow. goth basically implements this interface:
type Repository interface {
HandleAuth(echo.Context) error
HandleAuthCallback(echo.Context) error
}
HandleAuth
defines how authentication should be done for different identity providersHandleAuthCallback
is a callback called by the identity providers- this is where the access tokens (among additional data) are sent to
A share is the most basic entity used within gocial. A Share
is something that
will be shared via different identity providers. At the moment you can share
- an article
- contains an URL, a comment, a title and a list of providers where the article should be shared to
- a comment
- not implemented yet
// ArticleShare is an article to be shared via the share service
type ArticleShare struct {
URL string `json:"url" form:"url" validate:"required"`
Title string `json:"title" form:"title" validate:"required"`
Comment string `json:"comment" form:"comment" validate:"required"`
Providers string `json:"providers" form:"providers" validate:"required"`
}
// CommentShare is a comment to be shared via the share service
type CommentShare struct {
// TODO: Any other fields needed?
Comment string
}
gocial: ├── cli ├── docs ├── internal ├── lambda └── server
This is where the gocial specific domain code goes to. This includes entities, different services and the authentication part.
./internal ├── config │ └── config.go ├── entity │ ├── identity.go │ ├── providers.go │ └── share.go ├── identity │ ├── cookie_repository.go │ ├── file_repository.go │ └── repository.go ├── jwt │ └── token.go ├── oauth │ ├── goth_repository.go │ ├── repository.go │ └── service.go └── share ├── linkedin_repository.go ├── repository.go ├── service.go └── twitter_repository.go
./server ├── api.go ├── html │ ├── html.go │ ├── package.json │ ├── package-lock.json │ ├── postcss.config.js │ ├── static │ │ └── main.css │ ├── tailwind.config.js │ ├── tailwind.css │ ├── tailwind.js │ └── templates │ ├── about.html │ ├── auth │ ├── base.html │ ├── index.html │ ├── partials │ └── share ├── http.go ├── oauth.go └── share.go
This folder contains HTTP server specific functionalities:
/html
- here I put all the HTML templates and components (partials)
- I use tailwindCSS so there is a little bit of
npm
foo
http.go
- responsible for launching the HTTP server and setting up API routes
- renders HTML templates
api.go
- handles different API routes (e.g. sharing articles/comments)
oauth.go
- defines API endpoints for doing OAuth
Provides all gocial
functionalities via a CLI tool.
Runs the HTTP server as a Lambda function (hosted at netlify.com).
You can of course run it locally. However you’ll need to create Twitter and LinkedIn accordingly. Then you’ll need to set following environment variables (in .env
in the same folder):
export LINKEDIN_CLIENT_ID=xxx
export LINKEDIN_CLIENT_SECRET=xxx
export TWITTER_CLIENT_KEY=xxx
export TWITTER_CLIENT_SECRET=xxx
export TWITTER_ACCESS_TOKEN=xxx
export TWITTER_ACCESS_SECRET=xxx
Then you run make
$ make build
$ ./gocial --help
NAME:
gocial - A new cli application
USAGE:
gocial [global options] command [command options] [arguments...]
VERSION:
v0.1
AUTHOR:
Victor Dorneanu
COMMANDS:
authenticate, a Authenticate against identity providers
post, p Post some article
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help (default: false)
--version, -v print the version (default: false)
$ ./gocial a
____ __
/ __/___/ / ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.7.2
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on 127.0.0.1:3000
gocial
connects to twitter:
gocial
after successful logins:
Sharing an article: