Intro

Here we are once again! If you missed the last couple of posts I suggest reading them first to get a feeling for how our little prototype has evolved. This time around we’re going to be doing some refactoring to clean up a bit. The majority of what we’ll be doing is adding methods to our “database” struct.

The Method

What is a method? Well, you can think of it as a function that is attached to a struct. Or as A Tour of Go puts it

A method is a function with a special receiver argument.

In our code we have a struct GhostDatabase. We can add methods with the following signature where the receiver is our struct.

func (gd *GhostDatabase) getLength() int

We could then call the method with the following

var db GhostDatabase

...load JSON code here...

length := db.getLength()

OK so what’s our complete method look like? In the case of most of the methods we’ll be putting in place is pretty easy to reason about.

func (gd *GhostDatabase) getLength() int {
	return len(gd.Db[0].Data.Posts)
}

Many Methods

Now lets quickly knock out some useful methods. For the most part they do what they say on the tin, so I won’t go into detail on each one.

func (gd *GhostDatabase) getMobiledoc(i int) string {
	return gd.Db[0].Data.Posts[i].Mobiledoc
}

func (gd *GhostDatabase) getPostId(i int) string {
	return gd.Db[0].Data.Posts[i].ID
}

func (gd *GhostDatabase) getPostTitle(i int) string {
	return gd.Db[0].Data.Posts[i].Title
}

func (gd *GhostDatabase) getPostSlug(i int) string {
	return gd.Db[0].Data.Posts[i].Slug
}

func (gd *GhostDatabase) getPostStatus(i int) string {
	return gd.Db[0].Data.Posts[i].Status
}

func (gd *GhostDatabase) getPostCreatedAt(i int) time.Time {
	return gd.Db[0].Data.Posts[i].CreatedAt
}

func (gd *GhostDatabase) getPostUpdatedAt(i int) time.Time {
	return gd.Db[0].Data.Posts[i].UpdatedAt
}

func (gd *GhostDatabase) getPostPublishedAt(i int) time.Time {
	return gd.Db[0].Data.Posts[i].PublishedAt
}

func (gd *GhostDatabase) getPostFeatureImage(i int) string {
	return gd.Db[0].Data.Posts[i].FeatureImage
}

Tags

One thing that we haven’t looked into so far is retrieving the tags associated with a post. Since we’re working on adding methods now is a great time to work that out. Let’s talk through what we need to do.

First we need to loop through the “database” of post tags. From here we check to see if the PostID field matches with the post ID, pid, we pass into the function. We then assign the tag ID to a variable, tagId.

Next we’ll loop our way though the Tags “database” and check to see if the current tag id matches with tagId. If there is a match we append the tag name to a slice r. Once we’re done we return r.

Let’s see what that looks like in practice.

func (gd *GhostDatabase) getTags(pid string) []string {
	var r []string
	for i := 0; i < len(gd.Db[0].Data.PostsTags); i++ {
		if gd.Db[0].Data.PostsTags[i].PostID == pid {
			tagId := gd.Db[0].Data.PostsTags[i].TagID
			for j := 0; j < len(gd.Db[0].Data.Tags); j++ {
				if gd.Db[0].Data.Tags[j].ID == tagId {
					r = append(r, gd.Db[0].Data.Tags[j].Name)
				}
			}
		}
	}
	return r
}

Not to shabby.

Next Time

That’s all of our methods for now. With them out of the way I believe we have all the parts need to actually start writing our Markdown files!


Enjoy this post?
How about buying me a coffee?

Code Listing

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"
)

type GhostDatabase struct {
	Db []struct {
		Meta struct {
			ExportedOn int64  `json:"exported_on"`
			Version    string `json:"version"`
		} `json:"meta"`
		Data struct {
			Posts []struct {
				ID                   string      `json:"id"`
				UUID                 string      `json:"uuid"`
				Title                string      `json:"title"`
				Slug                 string      `json:"slug"`
				Mobiledoc            string      `json:"mobiledoc"`
				HTML                 string      `json:"html"`
				CommentID            string      `json:"comment_id"`
				Plaintext            string      `json:"plaintext"`
				FeatureImage         string      `json:"feature_image"`
				Featured             int         `json:"featured"`
				Type                 string      `json:"type"`
				Status               string      `json:"status"`
				Locale               interface{} `json:"locale"`
				Visibility           string      `json:"visibility"`
				EmailRecipientFilter string      `json:"email_recipient_filter"`
				AuthorID             string      `json:"author_id"`
				CreatedAt            time.Time   `json:"created_at"`
				UpdatedAt            time.Time   `json:"updated_at"`
				PublishedAt          time.Time   `json:"published_at"`
				CustomExcerpt        interface{} `json:"custom_excerpt"`
				CodeinjectionHead    interface{} `json:"codeinjection_head"`
				CodeinjectionFoot    interface{} `json:"codeinjection_foot"`
				CustomTemplate       interface{} `json:"custom_template"`
				CanonicalURL         interface{} `json:"canonical_url"`
			} `json:"posts"`
			PostsAuthors []struct {
				ID        string `json:"id"`
				PostID    string `json:"post_id"`
				AuthorID  string `json:"author_id"`
				SortOrder int    `json:"sort_order"`
			} `json:"posts_authors"`
			PostsMeta []interface{} `json:"posts_meta"`
			PostsTags []struct {
				ID        string `json:"id"`
				PostID    string `json:"post_id"`
				TagID     string `json:"tag_id"`
				SortOrder int    `json:"sort_order"`
			} `json:"posts_tags"`
			Roles []struct {
				ID          string    `json:"id"`
				Name        string    `json:"name"`
				Description string    `json:"description"`
				CreatedAt   time.Time `json:"created_at"`
				UpdatedAt   time.Time `json:"updated_at"`
			} `json:"roles"`
			RolesUsers []struct {
				ID     string `json:"id"`
				RoleID string `json:"role_id"`
				UserID string `json:"user_id"`
			} `json:"roles_users"`
			Settings []struct {
				ID        string      `json:"id"`
				Group     string      `json:"group"`
				Key       string      `json:"key"`
				Value     string      `json:"value"`
				Type      string      `json:"type"`
				Flags     interface{} `json:"flags"`
				CreatedAt time.Time   `json:"created_at"`
				UpdatedAt time.Time   `json:"updated_at"`
			} `json:"settings"`
			Tags []struct {
				ID                 string      `json:"id"`
				Name               string      `json:"name"`
				Slug               string      `json:"slug"`
				Description        interface{} `json:"description"`
				FeatureImage       interface{} `json:"feature_image"`
				ParentID           interface{} `json:"parent_id"`
				Visibility         string      `json:"visibility"`
				OgImage            interface{} `json:"og_image"`
				OgTitle            interface{} `json:"og_title"`
				OgDescription      interface{} `json:"og_description"`
				TwitterImage       interface{} `json:"twitter_image"`
				TwitterTitle       interface{} `json:"twitter_title"`
				TwitterDescription interface{} `json:"twitter_description"`
				MetaTitle          interface{} `json:"meta_title"`
				MetaDescription    interface{} `json:"meta_description"`
				CodeinjectionHead  interface{} `json:"codeinjection_head"`
				CodeinjectionFoot  interface{} `json:"codeinjection_foot"`
				CanonicalURL       interface{} `json:"canonical_url"`
				AccentColor        interface{} `json:"accent_color"`
				CreatedAt          time.Time   `json:"created_at"`
				UpdatedAt          time.Time   `json:"updated_at"`
			} `json:"tags"`
			Users []struct {
				ID              string      `json:"id"`
				Name            string      `json:"name"`
				Slug            string      `json:"slug"`
				Password        string      `json:"password"`
				Email           string      `json:"email"`
				ProfileImage    string      `json:"profile_image"`
				CoverImage      interface{} `json:"cover_image"`
				Bio             interface{} `json:"bio"`
				Website         interface{} `json:"website"`
				Location        interface{} `json:"location"`
				Facebook        interface{} `json:"facebook"`
				Twitter         interface{} `json:"twitter"`
				Accessibility   string      `json:"accessibility"`
				Status          string      `json:"status"`
				Locale          interface{} `json:"locale"`
				Visibility      string      `json:"visibility"`
				MetaTitle       interface{} `json:"meta_title"`
				MetaDescription interface{} `json:"meta_description"`
				Tour            interface{} `json:"tour"`
				LastSeen        time.Time   `json:"last_seen"`
				CreatedAt       time.Time   `json:"created_at"`
				UpdatedAt       time.Time   `json:"updated_at"`
			} `json:"users"`
		} `json:"data"`
	} `json:"db"`
}

type Mobiledoc struct {
	Version      string          `json:"version"`
	Markups      []interface{}   `json:"markups"`
	Atoms        []interface{}   `json:"atoms"`
	Cards        [][]interface{} `json:"cards"`
	Sections     [][]interface{} `json:"sections"`
	GhostVersion string          `json:"ghostVersion"`
}

func (gd *GhostDatabase) getLength() int {
	return len(gd.Db[0].Data.Posts)
}

func (gd *GhostDatabase) getMobiledoc(i int) string {
	return gd.Db[0].Data.Posts[i].Mobiledoc
}

func (gd *GhostDatabase) getPostId(i int) string {
	return gd.Db[0].Data.Posts[i].ID
}

func (gd *GhostDatabase) getPostTitle(i int) string {
	return gd.Db[0].Data.Posts[i].Title
}

func (gd *GhostDatabase) getPostSlug(i int) string {
	return gd.Db[0].Data.Posts[i].Slug
}

func (gd *GhostDatabase) getPostStatus(i int) string {
	return gd.Db[0].Data.Posts[i].Status
}

func (gd *GhostDatabase) getPostCreatedAt(i int) time.Time {
	return gd.Db[0].Data.Posts[i].CreatedAt
}

func (gd *GhostDatabase) getPostUpdatedAt(i int) time.Time {
	return gd.Db[0].Data.Posts[i].UpdatedAt
}

func (gd *GhostDatabase) getPostPublishedAt(i int) time.Time {
	return gd.Db[0].Data.Posts[i].PublishedAt
}

func (gd *GhostDatabase) getPostFeatureImage(i int) string {
	return gd.Db[0].Data.Posts[i].FeatureImage
}

func (gd *GhostDatabase) getTags(pid string) []string {
	var r []string
	for i := 0; i < len(gd.Db[0].Data.PostsTags); i++ {
		if gd.Db[0].Data.PostsTags[i].PostID == pid {
			tagId := gd.Db[0].Data.PostsTags[i].TagID
			for j := 0; j < len(gd.Db[0].Data.Tags); j++ {
				if gd.Db[0].Data.Tags[j].ID == tagId {
					r = append(r, gd.Db[0].Data.Tags[j].Name)
				}
			}
		}
	}
	return r
}

func main() {
	fmt.Println("ghost2hugo")

	file, err := os.Open("shindakun-dot-net.ghost.2022-03-18-22-02-58.json")
	if err != nil {
		fmt.Println(err)
	}

	defer file.Close()

	b, err := io.ReadAll(file)
	if err != nil {
		fmt.Println(err)
	}

	var db GhostDatabase

	err = json.Unmarshal(b, &db)
	if err != nil {
		fmt.Println(err)
	}

	for i := 0; i < db.getLength(); i++ {
		fmt.Println(db.getPostTitle(i))
		fmt.Println(db.getPostSlug(i))
		fmt.Println(db.getPostStatus(i))
		fmt.Println(db.getPostCreatedAt(i))
		fmt.Println(db.getPostUpdatedAt(i))
		fmt.Println(db.getPostPublishedAt(i))
		fmt.Println(db.getPostFeatureImage(i))

		id := db.getPostId(i)
		tags := db.getTags(id)
		fmt.Println(tags)

		c := strings.ReplaceAll(db.getMobiledoc(i), "`", "%'")
		cc := "`" + c + "`"

		ucn, err := strconv.Unquote(cc)
		if err != nil {
			fmt.Println(err, ucn)
		}

		ucn = strings.ReplaceAll(ucn, "%'", "`")

		var md Mobiledoc

		err = json.Unmarshal([]byte(ucn), &md)
		if err != nil {
			fmt.Println(err)
		}

		if reflect.ValueOf(md.Cards).Len() > 0 {
			card := md.Cards[0][1]
			bbb := card.(map[string]interface{})

			fmt.Println(bbb["markdown"])
		}

		fmt.Println("---")
	}
}