{"id":50445719,"url":"https://github.com/devsimsek/echo-basics","last_synced_at":"2026-05-31T21:30:42.443Z","repository":{"id":340056001,"uuid":"1164341051","full_name":"devsimsek/echo-basics","owner":"devsimsek","description":"Source code for echo-basics course (with a bit of everything :D)","archived":false,"fork":false,"pushed_at":"2026-02-23T00:58:20.000Z","size":54,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-21T00:02:06.611Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devsimsek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-23T00:55:36.000Z","updated_at":"2026-02-23T00:58:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/devsimsek/echo-basics","commit_stats":null,"previous_names":["devsimsek/echo-basics"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/devsimsek/echo-basics","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsimsek%2Fecho-basics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsimsek%2Fecho-basics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsimsek%2Fecho-basics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsimsek%2Fecho-basics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devsimsek","download_url":"https://codeload.github.com/devsimsek/echo-basics/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devsimsek%2Fecho-basics/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33750474,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T21:30:38.746Z","updated_at":"2026-05-31T21:30:42.430Z","avatar_url":"https://github.com/devsimsek.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# echo-basics\n\n\u003e A simple remote logging REST-ish API built with Go, Echo v5, GORM and Postgres. This is an in-person tutorial project — my students and I put this together to learn Go web development the right way, using the latest versions of everything.\n\nFair warning: I've intentionally left some bad practices in the code. If you spot them, open a PR — that's part of the exercise.\n\n---\n\n## What is this?\n\nA remote logging API. You send logs to it, it stores them, you can fetch and delete them. That's it. Simple on the surface, but there's enough going on under the hood to learn a lot from it — migrations, enums, context injection, rate limiting, and more.\n\n## Stack\n\n- [Go 1.25+](https://go.dev/)\n- [Echo v5](https://github.com/labstack/echo) — HTTP framework\n- [GORM v2](https://gorm.io/) — ORM\n- [gormigrate](https://github.com/go-gormigrate/gormigrate) — versioned migrations\n- [PostgreSQL](https://www.postgresql.org/) — database\n\n---\n\n## Getting Started\n\n### Prerequisites\n\n- Go 1.25+\n- PostgreSQL running locally (or remotely, doesn't matter)\n- `jq` (optional, but makes test output much nicer)\n\n### Environment\n\nCreate a `.dev.env` file in the project root (or a `.env` file — both are loaded):\n\n```\nDSN=postgres://user:pass@localhost:5432/dbname?sslmode=disable\nPORT=6070\nLIMIT_RATE=20\n```\n\n### Run\n\n```sh\ngo run .\n```\n\nThat's it. Migrations run automatically on startup — they're idempotent so running them multiple times won't blow anything up.\n\n---\n\n## Project Structure\n\n```\necho-basics/\n├── docs/               # documentation (you're reading it)\n├── migrations/         # gormigrate migration definitions\n├── models/             # GORM models, enums, app context\n├── modules/            # db init, env loader, utilities\n├── routes/             # route handlers\n├── tests/              # curl-based smoke tests\n├── server.go           # entry point\n├── go.mod\n└── LICENSE\n```\n\n---\n\n## API Reference\n\nBase path: `/api`\n\n### Health\n\n```\nGET /api/health\n```\n\nJust checking if we're alive.\n\n**Response 200**\n```json\n{\n  \"status\": \"ok\",\n  \"message\": \"Yeppers, seems good.\"\n}\n```\n\n---\n\n### Create Log\n\n```\nPOST /api/create\n```\n\nCreates a new log entry. `flag` defaults to `info` if not provided. Case-insensitive — send `INFO` or `info`, we don't mind.\n\n**Request body**\n```json\n{\n  \"flag\": \"info\",\n  \"message\": \"something happened\"\n}\n```\n\n**Allowed flag values**\n\n| Flag    | Level |\n|---------|-------|\n| `log`   | 0     |\n| `debug` | 1     |\n| `info`  | 2     |\n| `warn`  | 3     |\n| `error` | 4     |\n| `trace` | 5     |\n\n**Response 201**\n```json\n{\n  \"ID\": \"6af05bdd-2b64-4365-a600-b7d87a169da5\",\n  \"Flag\": \"info\",\n  \"Message\": \"something happened\",\n  \"Timestamp\": \"2026-02-23T00:00:00Z\"\n}\n```\n\n**Response 400** — bad payload or invalid flag  \n**Response 500** — something went wrong on our end\n\n---\n\n### List Logs\n\n```\nGET /api/list\n```\n\nReturns all logs. Yes, all of them. Pagination is a task left for you — go ahead and open a PR.\n\n**Response 200** — array of log objects\n\n---\n\n### Fetch by ID\n\n```\nGET /api/fetch/i/:id\n```\n\nFetch a single log by its UUID.\n\n**Response 200** — single log object  \n**Response 400** — that's not a UUID  \n**Response 404** — not found\n\n---\n\n### Fetch by Timestamp\n\n```\nGET /api/fetch/t/:timestamp\n```\n\nReturns the latest log at or before the given timestamp. Timestamp must be RFC3339 format (e.g. `2026-02-23T00:00:00Z`).\n\n**Response 200** — single log object  \n**Response 400** — bad timestamp  \n**Response 404** — nothing found before that timestamp\n\n---\n\n### Fetch by Flag\n\n```\nGET /api/fetch/f/:flag\n```\n\nReturns all logs with the given flag, ordered by timestamp descending. Case-insensitive.\n\n**Response 200** — array of log objects  \n**Response 400** — invalid flag value  \n**Response 500** — DB error\n\n---\n\n### Delete Log\n\n```\nDELETE /api/delete/:id\n```\n\nDeletes a log by UUID. There's a catch — you can only delete logs with a flag level **below 4** (i.e. `log`, `debug`, `info`, `warn`). `error` and `trace` are off limits.\n\n**Response 200** — deleted  \n**Response 400** — bad UUID  \n**Response 403** — flag level too high, not allowed  \n**Response 404** — log not found  \n**Response 500** — DB error\n\n---\n\n## Running Tests\n\nSmoke tests live under `tests/`. They use `curl` — no frameworks, no fuss.\n\n```sh\n# Run everything in order\n./tests/run_all.sh\n\n# Or run individually\n./tests/health.sh\n./tests/create.sh\n./tests/list.sh\n./tests/fetch_by_flag.sh INFO\n./tests/fetch_by_id.sh \u003cuuid\u003e\n./tests/delete.sh \u003cuuid\u003e\n```\n\nInstall `jq` to get pretty-printed output and automatic UUID extraction between steps.\n\n---\n\n## Migrations\n\nMigrations are versioned using `gormigrate` and run automatically at startup via `migrations.Run(db)`. They're idempotent — safe to re-run.\n\nCurrent migrations:\n- `1771799054_init_uuid_and_logs` — creates `uuid-ossp` extension, `log_flag` enum and the `logs` table.\n\nTo add a new migration, append a new `*gormigrate.Migration` entry in `migrations/migrations.go` with a unique incrementing ID.\n\n---\n\n## Contributing\n\nIf you spot something wrong or want to practice your Go and PR skills — go for it. There are intentional bad practices hidden in the codebase. Find them, fix them, open a PR.\n\n---\n\n## License\n\n[MIT](https://devsimsek.mit-license.org) — Metin Şimşek (devsimsek)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevsimsek%2Fecho-basics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevsimsek%2Fecho-basics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevsimsek%2Fecho-basics/lists"}