Codebase Analysis
Current Architecture (go-fed/apcore)
Here's the dependency map:
main.go
└── activity/application.go ← implements app.Application, S2SApplication, C2SApplication
├── activity/federation_follow.go ← S2S Follow callback
├── handlers/ ← HTTP handlers (inbox, outbox, user, etc.)
├── database/ ← wraps apcore services/models (Postgres)
└── vocab_builder/ ← builds custom AS2 types (Drink, Checkin, Venue...)
workers/
└── untappd.go ← cron job, uses apcore services + vocab
Key apcore dependencies across the codebase:
| Package |
Used for |
github.com/go-fed/apcore |
apcore.Run() — the entire server lifecycle |
github.com/go-fed/apcore/app |
app.Application, app.Framework, app.Router, app.Database interfaces |
github.com/go-fed/apcore/framework/config |
config.Config (loaded from config.ini) |
github.com/go-fed/apcore/framework/web |
web.Sessions |
github.com/go-fed/apcore/framework/oauth2 |
oauth2.Server |
github.com/go-fed/apcore/services |
services.Users, services.Followers, etc. |
github.com/go-fed/apcore/models |
DB models (Users, Inboxes, Outboxes, ...) |
github.com/go-fed/apcore/util |
util.ErrorLogger, util.InfoLogger, util.Context |
github.com/go-fed/apcore/paths |
paths.UUID, paths.Followers |
github.com/go-fed/activity/pub |
pub.SocialWrappedCallbacks, pub.Clock |
github.com/go-fed/activity/streams |
Custom vocab serialization/deserialization |
github.com/go-fed/activity/streams/vocab |
vocab.ActivityStreamsPerson, vocab.ActivityStreamsFollow, etc. |
The custom streams package is a forked version at git.feneas.org/socialtap/activity that adds Socialtap-specific types (SocialtapDrink, SocialtapCheckin, SocialtapVenue, SocialtapManufacturer).
The Migration Challenge
The two libraries have fundamentally different design philosophies:
| Aspect |
go-fed/apcore |
go-ap/activitypub |
| Role |
Full framework (server, DB, routing, OAuth) |
Vocabulary types only |
| Server lifecycle |
apcore.Run(app) handles everything |
You own the HTTP server |
| Routing |
app.Router abstraction |
Plain net/http / gorilla/mux |
| Database |
Built-in Postgres models & services |
You own the storage layer |
| Callbacks |
ApplyFederatingCallbacks, ApplySocialCallbacks |
You write the handlers |
| Config |
config.Config from config.ini |
You own config |
| Custom types |
Code-generated vocab extensions |
JSON-LD with custom properties |
Proposed Migration Plan
Stage 1 — Decouple from apcore internals
Introduce your own interfaces to wrap apcore so the rest of the code doesn't import it directly. This makes Stage 2 much safer.
Steps:
- Create
internal/config/config.go — own config struct, loaded from config.ini (keep same file format).
- Create
internal/logger/logger.go — thin wrapper replacing util.ErrorLogger/util.InfoLogger.
- Create
internal/storage/storage.go — define your own Storage interface (GetUser, GetFollowers, SaveActivity, etc.) backed by Postgres. Replace database/ calls to apcore services.
- Replace
util.Context with plain context.Context everywhere.
Stage 2 — Replace vocab types
Swap github.com/go-fed/activity/streams/vocab for github.com/go-ap/activitypub.
Key mapping:
| go-fed |
go-ap/activitypub |
vocab.ActivityStreamsPerson |
activitypub.Person |
vocab.ActivityStreamsFollow |
activitypub.Follow |
vocab.ActivityStreamsOrderedCollectionPage |
activitypub.OrderedCollectionPage |
vocab.ActivityStreamsImage |
activitypub.Image |
.GetActivityStreamsActor() |
.Actor (direct field) |
.GetActivityStreamsObject() |
.Object (direct field) |
.GetIRI() / .IsIRI() |
activitypub.IRI type directly |
Custom streams.NewSocialtapDrink() |
Plain activitypub.Object with Type + extra JSON-LD properties |
Stage 3 — Replace the server framework
Replace apcore.Run() with your own HTTP server using net/http + gorilla/mux, implementing:
- ActivityPub inbox/outbox endpoints
- HTTP Signatures (use
github.com/go-ap/httpsig)
- WebFinger (
github.com/go-ap/webfinger or similar)
- OAuth2 (use
golang.org/x/oauth2 or github.com/go-oauth2/oauth2)
- Cron jobs (keep
github.com/robfig/cron — no change needed)
Recommended New go.mod Dependencies
github.com/go-ap/activitypub ← replaces go-fed/activity vocab
github.com/go-ap/jsonld ← JSON-LD serialization
github.com/go-ap/httpsig ← HTTP Signatures for S2S federation
github.com/gorilla/mux ← already present, keep
github.com/robfig/cron ← already present, keep
github.com/google/uuid ← already present, keep
## Codebase Analysis
### Current Architecture (go-fed/apcore)
Here's the dependency map:
```
main.go
└── activity/application.go ← implements app.Application, S2SApplication, C2SApplication
├── activity/federation_follow.go ← S2S Follow callback
├── handlers/ ← HTTP handlers (inbox, outbox, user, etc.)
├── database/ ← wraps apcore services/models (Postgres)
└── vocab_builder/ ← builds custom AS2 types (Drink, Checkin, Venue...)
workers/
└── untappd.go ← cron job, uses apcore services + vocab
```
**Key apcore dependencies across the codebase:**
| Package | Used for |
|---|---|
| `github.com/go-fed/apcore` | `apcore.Run()` — the entire server lifecycle |
| `github.com/go-fed/apcore/app` | `app.Application`, `app.Framework`, `app.Router`, `app.Database` interfaces |
| `github.com/go-fed/apcore/framework/config` | `config.Config` (loaded from `config.ini`) |
| `github.com/go-fed/apcore/framework/web` | `web.Sessions` |
| `github.com/go-fed/apcore/framework/oauth2` | `oauth2.Server` |
| `github.com/go-fed/apcore/services` | `services.Users`, `services.Followers`, etc. |
| `github.com/go-fed/apcore/models` | DB models (Users, Inboxes, Outboxes, ...) |
| `github.com/go-fed/apcore/util` | `util.ErrorLogger`, `util.InfoLogger`, `util.Context` |
| `github.com/go-fed/apcore/paths` | `paths.UUID`, `paths.Followers` |
| `github.com/go-fed/activity/pub` | `pub.SocialWrappedCallbacks`, `pub.Clock` |
| `github.com/go-fed/activity/streams` | Custom vocab serialization/deserialization |
| `github.com/go-fed/activity/streams/vocab` | `vocab.ActivityStreamsPerson`, `vocab.ActivityStreamsFollow`, etc. |
The custom `streams` package is a **forked** version at `git.feneas.org/socialtap/activity` that adds Socialtap-specific types (`SocialtapDrink`, `SocialtapCheckin`, `SocialtapVenue`, `SocialtapManufacturer`).
---
## The Migration Challenge
The two libraries have fundamentally different design philosophies:
| Aspect | go-fed/apcore | go-ap/activitypub |
|---|---|---|
| **Role** | Full framework (server, DB, routing, OAuth) | Vocabulary types only |
| **Server lifecycle** | `apcore.Run(app)` handles everything | You own the HTTP server |
| **Routing** | `app.Router` abstraction | Plain `net/http` / gorilla/mux |
| **Database** | Built-in Postgres models & services | You own the storage layer |
| **Callbacks** | `ApplyFederatingCallbacks`, `ApplySocialCallbacks` | You write the handlers |
| **Config** | `config.Config` from `config.ini` | You own config |
| **Custom types** | Code-generated vocab extensions | JSON-LD with custom properties |
---
## Proposed Migration Plan
### Stage 1 — Decouple from apcore internals
Introduce your own interfaces to wrap apcore so the rest of the code doesn't import it directly. This makes Stage 2 much safer.
**Steps:**
1. Create `internal/config/config.go` — own config struct, loaded from `config.ini` (keep same file format).
2. Create `internal/logger/logger.go` — thin wrapper replacing `util.ErrorLogger`/`util.InfoLogger`.
3. Create `internal/storage/storage.go` — define your own `Storage` interface (GetUser, GetFollowers, SaveActivity, etc.) backed by Postgres. Replace `database/` calls to apcore services.
4. Replace `util.Context` with plain `context.Context` everywhere.
### Stage 2 — Replace vocab types
Swap `github.com/go-fed/activity/streams/vocab` for `github.com/go-ap/activitypub`.
**Key mapping:**
| go-fed | go-ap/activitypub |
|---|---|
| `vocab.ActivityStreamsPerson` | `activitypub.Person` |
| `vocab.ActivityStreamsFollow` | `activitypub.Follow` |
| `vocab.ActivityStreamsOrderedCollectionPage` | `activitypub.OrderedCollectionPage` |
| `vocab.ActivityStreamsImage` | `activitypub.Image` |
| `.GetActivityStreamsActor()` | `.Actor` (direct field) |
| `.GetActivityStreamsObject()` | `.Object` (direct field) |
| `.GetIRI()` / `.IsIRI()` | `activitypub.IRI` type directly |
| Custom `streams.NewSocialtapDrink()` | Plain `activitypub.Object` with `Type` + extra JSON-LD properties |
### Stage 3 — Replace the server framework
Replace `apcore.Run()` with your own HTTP server using `net/http` + `gorilla/mux`, implementing:
- ActivityPub inbox/outbox endpoints
- HTTP Signatures (use `github.com/go-ap/httpsig`)
- WebFinger (`github.com/go-ap/webfinger` or similar)
- OAuth2 (use `golang.org/x/oauth2` or `github.com/go-oauth2/oauth2`)
- Cron jobs (keep `github.com/robfig/cron` — no change needed)
---
## Recommended New `go.mod` Dependencies
```
github.com/go-ap/activitypub ← replaces go-fed/activity vocab
github.com/go-ap/jsonld ← JSON-LD serialization
github.com/go-ap/httpsig ← HTTP Signatures for S2S federation
github.com/gorilla/mux ← already present, keep
github.com/robfig/cron ← already present, keep
github.com/google/uuid ← already present, keep
```