At the time of writing, this contains the second version of the archive, released for Winter 2025.
Go was chosen for this project because of its simplicity, maintainability, high quality and low carbon footprint. In comparison to higher level languages like Python and JavaScript, Go is a compiled language1. This makes it more efficient in terms of energy consumption.
The ulimate aim in this project was to use SQLite as the database backend.
SQLite was chosen for this project because it is a lightweight, disk-based database2. It allows us to keep all of the archive's data in a single file, making it easier to store and transport.
In the event of a climate collapse, the database will still be readable and usable and can easily be reproduced.
In comparison to a more traditional database like PostgreSQL, SQLite is also more energy efficient.
The eventual ambition is for the project itself to contain its own backend which uses NocoDB. Currently NocoDB is used as an interface for the backend of the archive, which does ultimately write to a SQLite database stored on disk. At some point we will replace calls to the NocoDB API3 with direct reads of an SQLite database, but it was decided that this was too much complexity for now, as the NocoDB interface provided a lot of power.
Accessing the NocoDB database directly from SQLite clients is possible, so the archive does not ultimate depend on NocoDB. All parts of the archive can therefore be replaced.
This section explains how the archive takes story data from NocoDB and turns it into a complete static website. If you're new to maintaining the archive, this will help you understand how everything fits together.
Before we dive in, let's be clear about what "static site" means:
A static site is just a folder full of regular HTML4 files - like the websites from the early days of the internet. When someone visits the archive, their browser just downloads and displays these HTML files directly. There's no database query happening, no server generating pages on-the-fly.
Think of it like this:
- Dynamic sites (like Facebook): When you visit, the server runs code, fetches data from a database, builds the page right then, and sends it to you. Different every time!
- Static sites (like this archive): All the pages are already built and sitting in a folder. When you visit, the server just sends you the pre-made HTML file. Simple!
Why we use a static site:
- Fast: No waiting for a server to build pages - they're already built
- Simple: Just HTML files, so you can host them almost anywhere
- Resilient: If something goes wrong with the hosting, you still have all your HTML files. Resilience against all kinds of disaster, especially climate related, is a key goal of this archive
- Low energy: Static files use way less server resources than dynamic sites
- Future-proof: In 20 and even 100 years, HTML files will still work, even if the database software and all other technologies in the archive are obsolete
The "Generator" Part: We don't write all those HTML files by hand! The archive generates them automatically from the NocoDB data and templates. You run the build command, it creates all the HTML files, and then those files get uploaded to a web hosting service called Render so that people can visit the archive with their web browser.
So: Database + Templates → Build Process → HTML Files → Upload to Render → Live Archive
The archive is a static site generator. It takes data we have an makes a static site, as described above. It fetches data from NocoDB, processes images, fills in HTML templates5, and creates a complete website of HTML files. The generated site is then uploaded for hosting. No database or server-side code is needed once the site is built.
graph TB
A[NocoDB Database<br/>Stories live here] -->|Fetch stories via API| B[Archive Builder<br/>Go program]
B -->|Process & resize| C[Images]
B -->|Fill with data| D[HTML Templates]
C --> E[Generated Website<br/>Just HTML files in a folder!]
D --> E
E -->|Upload| F[Render Static Site<br/>Visitors download these HTML files]
style A fill:#e1f5ff
style B fill:#fff4e1
style E fill:#e8f5e9
style F fill:#f3e5f5
The code is organised into layers, each with a specific job. This keeps things tidy and makes it easier to change one part without breaking others.
graph TB
subgraph "Entry Point"
CMD[cmd/archive/main.go<br/>Starts everything]
end
subgraph "Generation Layer"
GEN[internal/generate/<br/>Creates HTML pages]
ASSETS[internal/generate/assets.go<br/>Processes images]
end
subgraph "Store Layer - The Adapter"
ADAPTER[internal/store/adapter.go<br/>Defines how to fetch data]
NOCODB[internal/store/nocodb_adapter.go<br/>Talks to NocoDB]
end
subgraph "Data Layer"
TYPES[internal/nocodb/types.go<br/>Translates NocoDB format]
STORY[data/story.go<br/>Story structs everyone uses]
end
subgraph "Templates"
TMPL[templates/<br/>HTML templates]
end
CMD --> GEN
CMD --> ASSETS
GEN --> ADAPTER
ADAPTER --> NOCODB
NOCODB --> TYPES
TYPES --> STORY
GEN --> TMPL
style CMD fill:#ffebee
style GEN fill:#e1f5ff
style ASSETS fill:#e1f5ff
style ADAPTER fill:#fff9c4
style NOCODB fill:#fff9c4
style TYPES fill:#e8f5e9
style STORY fill:#e8f5e9
style TMPL fill:#f3e5f5
Here's what happens when you run go run ./cmd/archive:
flowchart TD
Start([Run: go run ./cmd/archive]) --> Config[Load settings from .env file]
Config --> Fetch[Fetch all stories from NocoDB]
Fetch --> Images[Process images<br/>Resize and convert to WebP]
Images --> Pages[Generate all HTML pages]
Pages --> Home[Homepage]
Pages --> Stories[Story pages<br/>One for each story]
Pages --> Index[Index pages<br/>Themes, types, weather, etc.]
Home --> CSS[Copy CSS and assets]
Stories --> CSS
Index --> CSS
CSS --> Done([Done! Files in out/ folder])
style Start fill:#e8f5e9
style Fetch fill:#e1f5ff
style Images fill:#fff9c4
style Pages fill:#ffebee
style Done fill:#e8f5e9
The store layer is like a translator. The rest of the code just asks "give me stories" and doesn't need to know if they're coming from NocoDB, SQLite, or anywhere else.
Why we have this:
- We can swap NocoDB for SQLite later without rewriting everything
- The code is cleaner because fetching data is separate from displaying it
- Testing is easier because we can create fake adapters
// The interface - what the adapter promises to do
type DataAdapter interface {
GetAllStories() ([]data.Story, error)
GetStoryByID(id string) (data.Story, error)
GetStoriesForTheme(theme string) ([]data.Story, error)
// ... and so on
}
// The NocoDB version - how we do it right now
type NocoDBAdapter struct {
client *nocodb.Client
}
// In the future, we might have:
type SQLiteAdapter struct {
db *sql.DB
}The rest of the code just uses GetAdapter() and doesn't care which adapter it is.
NocoDB sends data in one format, but we need it in another. Here's how a story transforms as it moves through the system:
graph LR
A["NocoDB JSON<br/>Field: 'Image / video / sound'<br/>Field: 'What was/is/if'"]
-->|Parse| B["NocoDBStoryDTO<br/>ImageVideoSound interface<br/>WhatWasIsIf interface"]
-->|Convert| C["Story Struct<br/>ImageVideoSound string<br/>WhatWasIsIf []WhatWasIsIf"]
-->|Render| D["HTML Template<br/>.Story.ImageVideoSound<br/>range .Story.WhatWasIsIf"]
style A fill:#ffebee
style B fill:#fff9c4
style C fill:#e8f5e9
style D fill:#f3e5f5
Step by step:
- NocoDB JSON: Raw data with field names like "Image / video / sound" (notice the spaces)
- NocoDBStoryDTO: A Go struct6 with JSON7 tags that match NocoDB's field names exactly
- Story Struct: The proper struct everyone uses, with nice Go field names and proper types
- HTML Template: The template accesses fields like
{{.Story.Finding}}to display them
The conversion happens in internal/nocodb/types.go. It handles tricky things like:
- Converting things like
["Theme A", "Theme B"]into proper Go structs with URLs and colours - Parsing attachment JSON into StoryAttachment objects
- Turning
nullinto sensible defaults instead of crashing
Images need special treatment. We resize them into different sizes and convert them to WebP8 format (which loads faster and uses less bandwidth).
graph TB
A[Original Image<br/>photo.jpg<br/>3000×2000px] --> B[Read from images/ folder]
B --> C{Resize into 3 versions}
C --> D[Thumbnail<br/>400×267px]
C --> E[Medium<br/>1024×683px]
C --> F[Large<br/>2048×1365px]
D --> G[Convert to WebP]
E --> G
F --> G
G --> H[Save to out/images/processed/]
H --> I[Templates reference<br/>processed/photo-thumb.webp<br/>processed/photo-medium.webp<br/>processed/photo-large.webp]
style A fill:#ffebee
style G fill:#e8f5e9
style I fill:#f3e5f5
Why WebP?
- Much smaller file sizes than JPG or PNG
- Faster page loading for visitors
- Better for accessibility (people on slow connections)
- More environmentally friendly (less data transfer = less energy)
When you run the archive, here's what actually happens:
graph TB
START([Run: go run ./cmd/archive]) --> CONFIG[Load configuration<br/>from .env file]
CONFIG --> ADAPTER[Initialize NocoDB adapter]
ADAPTER --> CACHE[Warm the cache<br/>Fetch all stories once]
CACHE --> IMAGES{Skip images?}
IMAGES -->|No| PROCESS[Process all images<br/>Resize & convert to WebP]
IMAGES -->|Yes -s flag| SKIP[Skip image processing]
PROCESS --> PAGES
SKIP --> PAGES[Generate all HTML pages]
PAGES --> HOME[Homepage]
PAGES --> ARCHIVE[Archive page]
PAGES --> WANDER[Wander page]
PAGES --> STORIES[Individual story pages<br/>One per story]
PAGES --> THEMES[Theme index pages]
PAGES --> TYPES[Type index pages]
PAGES --> WEATHER[Weather index pages]
PAGES --> OTHER[Other index pages<br/>Gifted By, Time Period, etc.]
HOME --> CSS
ARCHIVE --> CSS
WANDER --> CSS
STORIES --> CSS
THEMES --> CSS
TYPES --> CSS
WEATHER --> CSS
OTHER --> CSS
CSS[Copy CSS to out/] --> DONE{Development mode?}
DONE -->|Yes -d flag| SERVER[Start local server<br/>http://localhost:8080<br/>Watch for template changes]
DONE -->|No| FINISH([Done! Website in out/ folder])
style START fill:#e8f5e9
style PROCESS fill:#fff9c4
style PAGES fill:#e1f5ff
style SERVER fill:#f3e5f5
style FINISH fill:#e8f5e9
Development Mode (-d flag):
- Builds the website
- Starts a local web server on http://localhost:8080
- Watches for template changes
- Press Enter to regenerate pages
- Perfect for testing changes quickly
Production Mode (no flags):
- Builds the website (generates all the HTML files)
- Exits when done
- Render uses this mode when deploying
- The generated
out/folder (full of HTML files) gets served to visitors by Render
If you need to make changes, here's where to look:
| File | What it does |
|---|---|
cmd/archive/main.go |
Entry point - starts everything |
internal/config/config.go |
Loads settings from .env |
internal/store/adapter.go |
Defines the adapter interface |
internal/store/nocodb_adapter.go |
NocoDB implementation |
internal/nocodb/types.go |
Translates NocoDB → Go structs |
data/story.go |
Main Story struct & helpers |
data/tags.go |
Theme, Type, Weather structs |
internal/generate/generate.go |
Creates all HTML pages |
internal/generate/assets.go |
Processes images & copies CSS |
templates/*.html |
HTML templates |
Want to add a new field to stories?
- Add to
NocoDBStoryDTOininternal/nocodb/types.go - Add to
Storystruct indata/story.go - Map in the conversion function in
internal/nocodb/types.go - Use in template with
{{.Story.NewField}}
Want to add a new page type?
- Create a template in
templates/ - Add a generation function in
internal/generate/generate.go - Call it from
cmd/archive/main.goin bothgenerateArchive()andhotRegenerate()
Want to change how pages look?
- Edit
css/styles.cssfor styling - Edit templates in
templates/for structure - Images in
images/get processed automatically
By default, the archive fetches fresh story data from NocoDB on each run.
Disk caching (debug-cache-nocodb.json) is debug-only and must be explicitly enabled with:
go run ./cmd/archive --debug-disk-cacheIf you are using debug disk cache and need fresh data, delete the cache file:
rm debug-cache-nocodb.jsonImages not showing up?
- Check
images/folder has the files - Make sure you're not using
-sflag (which skips image processing) - Delete
debug-cache-nocodb.jsonand rebuild
Template changes not appearing?
- In development mode, press Enter to regenerate
- Or restart the archive
Story data seems stale?
- If you enabled debug disk cache, delete
debug-cache-nocodb.json - Rebuild the archive
Build failing?
- Check your
.envfile has all the NocoDB settings (environment variables9) - Make sure NocoDB is accessible
- Check for error messages in the console
The archive currently deploys to Render as a static site. Render handles the entire build and deployment process automatically.
How it works:
When you push commits to the main branch, Render automatically:
- Detects the change in the repository
- Runs the build command:
go run ./cmd/archive(production mode) - Generates all the HTML files into the
out/folder - Serves these HTML files to visitors
That's it! Render does all the work - building the site and hosting it. No GitHub Actions or manual deployment needed.
Before you start, you'll need Git10 installed on your Mac to download the repository.
You have two options:
Option 1: GitHub Desktop (Recommended for beginners)
GitHub Desktop includes Git and provides a visual interface:
- Download from desktop.github.com
- Install the application
- Git will be available in your terminal automatically
Option 2: Command Line
Install Git via Xcode Command Line Tools (takes 5-15 minutes):
xcode-select --installA dialogue will appear - click "Install" and wait for it to complete.
To verify Git is installed:
git --versionWe've created an automated setup script that handles everything for you.
- Download the repository11.
git clone https://github.com/commonknowledge/community-climate-justice-archive.git
cd community-climate-justice-archive- Run the setup script.
bash setup.shThe script will:
- Check for Homebrew12 and install it if needed
- Check for Go and prompt you to install it if needed
- Check for code editors (VS Code, Sublime Text, Atom) and offer to install VS Code if none are found
- Ask you for your NocoDB configuration (endpoint, API key, table ID)
- Build the archive
- Optionally launch it in development mode
That's it! The archive will be running at http://localhost:8080.
If you prefer to set things up manually:
- Install Go.
brew install go- Download the repository11.
You can do this on the command line13 with the following, or use your favoured Git10 client.
Its a bit repository, so be patient while it downloads.
git clone https://github.com/commonknowledge/community-climate-justice-archive.git- Create your
.envfile.
Copy env.example to .env and add your NocoDB credentials:
cp env.example .env
# Then edit .env with your text editor- Run the archive in development mode.
Enter the repository directory in a terminal14.
cd community-climate-justice-archiveThen start the archive in development mode. This will initially compile the archive.
go run ./cmd/archive -dThis will launch a development server at http://localhost:8080.
Edit the file css/styles.css.
If you are running the archive in development mode, then the CSS will automatically be copied to the directory that the development webserver serves.
When you've made your change, simply refresh the page to see the effect.
The HTML templates are located in the templates directory.
If you are running the archive in development mode, press enter to regenerate the archive. Your changes in these HTML templates will then be picked up.
Templates use Go's standard html/template package syntax.
You can find documentation for Go's templating language at:
- html/template package documentation
- text/template package documentation (html/template builds on this)
Key template features include:
{{.Something}}for outputting information that the main program gives to templates.{{if .SomeOtherThing}}...{{end}}for making decisions and displaying different things based on these.{{range .Stories}}...{{end}}for looping over a group of things, in the case of the archive mostly stories.
The archive templates should be straight forward to understand what they do, but to start you off, here is a brief description of each of them.
Main page templates:
- homepage.html: The main landing page.
- story.html: Displays individual stories with their image, details (type, date, location, etc.), and related stories.
- archive.html: The main archive page with filtering functionality to browse all stories.
- wander.html: A randomized/shuffled view of all stories for serendipitous discovery.
Pages for different ways to organize stories:
- theme-index.html: Shows all stories related to a specific theme.
- type-index.html: Shows all stories of a particular type.
- weather-index.html: Shows all stories that were created in a particular weather condition.
- giftedby-index.html: Shows all stories gifted or co-created by a specific person or organization.
- timeperiod-index.html: Shows all stories from a specific time period.
- whatwasisif-index.html: Shows all stories categorized by "What Was/Is/If" framework.
- scalepermanence-index.html: Shows all stories with a specific scale of permanence classification.
Shared pieces (used by other templates):
- partials/header.html: The top part of every page with the site title and navigation menu.
- partials/footer.html: The bottom part of every page with project information and contact details.
- partials/stories-list.html: The code that displays stories in a grid - used by many different pages.
- partials/grid-code.html: The code that makes story previews pop up when you hover over them.
When you need to add a simple field (text, date, number, etc.) from NocoDB to display in the individual story template, follow these steps:
Note: These instructions are for simple field types only. MultiSelect, Links, and LinkToAnotherRecord fields require different treatment and specialized parsing logic.
File: internal/nocodb/types.go
Add the new field to the NocoDBStoryDTO struct with the proper JSON tag matching the NocoDB field name:
type NocoDBStoryDTO struct {
// ... existing fields ...
NewField interface{} `json:"New Field Name"`
}File: data/story.go
Add the corresponding field to the Story struct (around lines 27-63):
type Story struct {
// ... existing fields ...
NewField string
}File: internal/nocodb/types.go
Update the NocoDBRecordToStoryWithClient function (around lines 89-125) to map from DTO to Story:
story := data.Story{
// ... existing mappings ...
NewField: toString(dto.NewField),
}File: templates/story.html
Add the field display in the template using Go template syntax:
<!-- Simple field display -->
{{.Story.NewField}}
<!-- Conditional display -->
{{if .Story.NewField}}
<p>{{.Story.NewField}}</p>
{{end}}
<!-- With HTML structure -->
<div class="new-field">
<label>New Field:</label>
<span>{{.Story.NewField}}</span>
</div>This process works for these NocoDB field types:
- SingleLineText - Maps to
string - LongText - Maps to
string - Number - Maps to
string(converted viatoString()) - Date - Maps to
string - DateTime - Maps to
string - URL - Maps to
string - Email - Maps to
string - PhoneNumber - Maps to
string
The following field types require specialized handling and are not covered by these instructions:
- MultiSelect - Requires custom parsing to convert to
[]Theme,[]Type, or[]Weatherstructs - Links - Requires relationship resolution and connection caching
- LinkToAnotherRecord - Requires relationship resolution and connection caching
- Attachment - Requires image processing and JSON conversion
- Checkbox - Requires boolean conversion
- SingleSelect - May require enum/option handling
For these complex types, refer to existing examples in the codebase:
- MultiSelect: See
ParseThemesFromNocoDB(),ParseTypesFromNocoDB(),ParseWeatherFromNocoDB() - Links/LinkToAnotherRecord: See
fetchStoryConnectionsDirect() - Attachment: Raw JSON marshalling is used (see
ImageVideoSoundfield mapping inNocoDBRecordToStoryWithClient)
File: internal/generate/generate.go
If the field should be available for client-side filtering, add it to the StoryData struct (around lines 82-96):
type StoryData struct {
// ... existing fields ...
NewField string `json:"newField"`
}Run the field analysis utility to verify the field shows as FULLY_MAPPED:
go run cmd/analyze-api-fields/main.goThe field should appear with processing_category: "standard" and status: "FULLY_MAPPED" if properly integrated.
The data flows through these components:
NocoDB API → NocoDBStoryDTO → Story → StoryPage → story.html template
- NocoDB API returns raw field data
- NocoDBStoryDTO receives and structures the raw data
- Story struct gets converted, typed data
- StoryPage wraps Story for template context
- story.html template renders the field using
{{.Story.FieldName}}
internal/nocodb/types.go- DTO struct + conversion logicdata/story.go- Story struct definitiontemplates/story.html- Template displayinternal/generate/generate.go- (only if field used in filtering/JSON export)
When you need to add a MultiSelect field from NocoDB (like Themes, Types, or Weather), follow this comprehensive process. MultiSelect fields are used for tagging and categorization, requiring specialized handling.
Note: This process is for MultiSelect fields that function as tags/categories. For other complex field types (Links, LinkToAnotherRecord, Attachments), refer to existing examples in the codebase.
File: internal/nocodb/types.go
Add the new field to the NocoDBStoryDTO struct:
type NocoDBStoryDTO struct {
// ... existing fields ...
NewMultiSelectField interface{} `json:"New Field Name"`
}File: data/story.go
Create a new struct to represent individual tag/category items:
type NewFieldType struct {
Title string // Display name
URL string // Generated URL slug
Colour string // Generated hex color
}File: data/story.go
Add the field as a slice of the new type:
type Story struct {
// ... existing fields ...
NewMultiSelectField []NewFieldType
}File: internal/nocodb/types.go
Create a parsing function similar to existing ones (ParseThemesFromNocoDB, ParseTypesFromNocoDB, ParseWeatherFromNocoDB):
func ParseNewFieldFromNocoDB(field interface{}) ([]data.NewFieldType, error) {
if field == nil {
return []data.NewFieldType{}, nil
}
fieldStr, ok := field.(string)
if !ok {
return []data.NewFieldType{}, fmt.Errorf("expected string, got %T", field)
}
if fieldStr == "" {
return []data.NewFieldType{}, nil
}
// Split comma-separated values from NocoDB
items := strings.Split(fieldStr, ",")
var result []data.NewFieldType
for _, item := range items {
item = strings.TrimSpace(item)
if item != "" {
result = append(result, data.NewFieldType{
Title: item,
URL: util.Slugify(item),
Colour: data.TitleToHexColor(item),
})
}
}
return result, nil
}File: internal/nocodb/types.go
Update the NocoDBRecordToStoryWithClient function:
func NocoDBRecordToStoryWithClient(record map[string]interface{}, client *Client) (data.Story, error) {
// ... existing DTO conversion ...
// Convert new field
newField, err := ParseNewFieldFromNocoDB(dto.NewMultiSelectField)
if err != nil {
log.Printf("Warning: failed to parse new field: %v", err)
newField = []data.NewFieldType{}
}
story := data.Story{
// ... existing mappings ...
NewMultiSelectField: newField,
}
return story, nil
}File: templates/story.html
Add the field display using the tag pattern:
{{if $story.NewMultiSelectField}}
<div class="story-tags">
<span class="story-tags-label">New Field:</span>
{{range $story.NewMultiSelectField}}
<a href="/newfield/{{.URL}}.html" class="tag" style="background-color: {{.Colour}};">{{.Title}}</a>
{{end}}
</div>
{{end}}File: internal/store/ (add to appropriate adapter file)
Add functions to retrieve and filter by the new field:
func (s *SQLiteAdapter) GetNewFieldTypes() []data.NewFieldType {
// Get all unique values for the new field
// Similar to GetThemes(), GetTypes(), GetWeather()
}
func (s *SQLiteAdapter) GetStoriesForNewFieldType(fieldValue string) ([]data.Story, error) {
// Filter stories by the new field value
// Similar to GetStoriesForTheme(), GetStoriesForType(), GetStoriesForWeather()
}File: internal/generate/generate.go
Add a function to generate individual pages for each field value:
func WriteNewFieldIndexPages(stories []data.Story, store store.Adapter) error {
newFieldTypes := store.GetNewFieldTypes()
for _, fieldType := range newFieldTypes {
fieldStories, err := store.GetStoriesForNewFieldType(fieldType.Title)
if err != nil {
return fmt.Errorf("failed to get stories for new field %s: %w", fieldType.Title, err)
}
page := data.Page{
Title: fieldType.Title,
Stories: fieldStories,
NewFieldType: &fieldType,
}
filename := fmt.Sprintf("newfield/%s.html", fieldType.URL)
if err := writePageToFile(filename, "newfield-index.html", page); err != nil {
return fmt.Errorf("failed to write new field page %s: %w", filename, err)
}
}
return nil
}File: templates/newfield-index.html
Create a template similar to theme-index.html, type-index.html, weather-index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{.Title}} - Community Climate Justice Archive</title>
<!-- ... head content similar to other index templates ... -->
</head>
<body>
<div class="page-container">
<h1>{{.Title}}</h1>
<p>Stories tagged with "{{.Title}}"</p>
<div class="stories-grid">
{{range .Stories}}
<!-- ... story display similar to other index templates ... -->
{{end}}
</div>
</div>
</body>
</html>File: css/styles.css
Ensure the new field tags have consistent styling with existing tags:
/* New field tags should inherit existing .tag styles */
.story-tags .tag {
/* Existing tag styles will apply */
}File: cmd/archive/main.go
Add calls to your new index generation functions in both generateArchive() and hotRegenerate() functions:
// In generateArchive() function, after WriteWeatherIndexes():
if err := generate.WriteNewFieldIndexPages(); err != nil {
return fmt.Errorf("failed to write new field indexes: %v", err)
}
// In hotRegenerate() function, after WriteWeatherIndexes():
if err := generate.WriteNewFieldIndexPages(); err != nil {
return fmt.Errorf("failed to write new field indexes: %v", err)
}Important: Without this step, the index pages won't be generated and the tag links won't work!
Status: DEPRECATED - No longer needed
The cmd/consolidate-image-fields/ tool was created to migrate data from legacy SourceImage and Image fields into the unified ImageVideoSound field. This migration has been completed.
Important Notes:
- The tool is idempotent and safe to run multiple times
- Running it now will not cause any issues as it will detect no changes are needed
- The tool is kept for historical reference and potential future migrations
- All stories now use only the
ImageVideoSoundfield for attachments
Usage (if needed):
go build -o consolidate-image-fields ./cmd/consolidate-image-fields
./consolidate-image-fields --checksum # Verify consolidation is completeFootnotes
-
Compiled language: A programming language where code is translated into machine instructions before running, making it faster and more efficient. Learn more ↩
-
Database: A structured system for storing and organising data. Think of it like a sophisticated filing cabinet for information. Learn more ↩
-
API (Application Programming Interface): A way for programs to talk to each other. NocoDB's API lets us request story data over the internet. Learn more ↩
-
HTML (HyperText Markup Language): The standard language for creating web pages. HTML files tell browsers how to display content. Learn more ↩
-
Templates: Pre-designed files with placeholders that get filled in with actual data. Like a form letter where you fill in the name and address. Learn more ↩
-
Struct (Structure): In Go, a struct is a custom data type that groups related pieces of information together. Like a form with labelled fields. Learn more ↩
-
JSON (JavaScript Object Notation): A text format for storing and transmitting data that's easy for both humans and computers to read. Learn more ↩
-
WebP: A modern image format that creates smaller file sizes than JPEG or PNG while maintaining quality, making pages load faster. Learn more ↩
-
Environment variables: Configuration settings stored outside your code, often in a
.envfile. They keep sensitive information like passwords separate from the code. Learn more ↩ -
Git: A version control system that tracks changes to files over time, letting you collaborate with others and revert to previous versions if needed. Learn more ↩ ↩2
-
Repository: A storage location for your project files and their entire revision history. Often shortened to "repo". Learn more ↩ ↩2
-
Homebrew: A package manager for macOS that makes installing and managing software much easier. Think of it as an app store for developer tools. Learn more ↩
-
Command line: A text-based interface where you type commands to interact with your computer, rather than clicking with a mouse. Learn more ↩
-
Terminal: A program that provides the command line interface. On macOS, it's called "Terminal"; on Windows, "Command Prompt" or "PowerShell". Learn more ↩