/gocial

Social media interactions - The Gopher way

Primary LanguageHTML

docs/images/2022-12-screenshot.png

About

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.

Features

  • Architecture
    • Serverless
    • 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).
  • 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
    • 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

Architecture

docs/images/architecture.png

Business domain

  • 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

Identity

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"`
}

OAuth

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 providers
  • HandleAuthCallback is a callback called by the identity providers
    • this is where the access tokens (among additional data) are sent to

Share

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
}

Project layout

gocial:

β”œβ”€β”€ cli
β”œβ”€β”€ docs
β”œβ”€β”€ internal
β”œβ”€β”€ lambda
└── server

/internal

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

./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

/cli

Provides all gocial functionalities via a CLI tool.

/lambda

Runs the HTTP server as a Lambda function (hosted at netlify.com).

Run it

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

More screenshots

gocial connects to twitter:

docs/images/gocial-connects-to-twitter.png

gocial after successful logins:

docs/images/gocial-after-successful-logins.png

Sharing an article:

docs/images/gocial-share-article.png