summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.envrc17
-rw-r--r--.gitignore13
-rw-r--r--.proverc2
-rw-r--r--build.zig98
-rw-r--r--build.zig.zon34
-rw-r--r--flake.lock807
-rw-r--r--flake.nix99
-rw-r--r--src/main.zig29
-rw-r--r--src/root.zig10
-rw-r--r--src/schema.sql78
-rw-r--r--test/00-permissions.sql25
-rw-r--r--test/01-users.sql14
12 files changed, 1226 insertions, 0 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..31c70e2
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,17 @@
+if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
+ source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
+fi
+
+nix_direnv_watch_file flake.nix
+nix_direnv_watch_file flake.lock
+if ! use flake . --impure
+then
+ echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
+fi
+
+export PGSOCK=.devenv/run/postgres/.s.PGSQL.5432
+export PGDATABASE=bikeshed
+export PGUSER=$PGDATABASE
+export PGPASS=$PGUSER
+
+export DATABASE_URL=postgresql://$PGUSER:$PGPASS@/$PGNAME
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4d27c96
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+# Devenv
+.devenv*
+devenv.local.nix
+
+# direnv
+.direnv
+
+# pre-commit
+.pre-commit-config.yaml
+
+# Zig
+zig-cache
+zig-out
diff --git a/.proverc b/.proverc
new file mode 100644
index 0000000..93ed584
--- /dev/null
+++ b/.proverc
@@ -0,0 +1,2 @@
+test/
+--ext .sql
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..454f20f
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,98 @@
+const std = @import("std");
+
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) void {
+ // Standard target options allows the person running `zig build` to choose
+ // what target to build for. Here we do not override the defaults, which
+ // means any target is allowed, and the default is native. Other options
+ // for restricting supported target set are available.
+ const target = b.standardTargetOptions(.{});
+
+ // Standard optimization options allow the person running `zig build` to select
+ // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
+ // set a preferred release mode, allowing the user to decide how to optimize.
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lib = b.addStaticLibrary(.{
+ .name = "bikeshed",
+ // In this case the main source file is merely a path, however, in more
+ // complicated build scripts, this could be a generated file.
+ .root_source_file = .{ .path = "src/root.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+
+ // This declares intent for the library to be installed into the standard
+ // location when the user invokes the "install" step (the default step when
+ // running `zig build`).
+ b.installArtifact(lib);
+
+ const pg = b.dependency("pg", .{
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const exe = b.addExecutable(.{
+ .name = "bikeshed",
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+
+ exe.root_module.addImport("pg", pg.module("pg"));
+
+ // This declares intent for the executable to be installed into the
+ // standard location when the user invokes the "install" step (the default
+ // step when running `zig build`).
+ b.installArtifact(exe);
+
+ // This *creates* a Run step in the build graph, to be executed when another
+ // step is evaluated that depends on it. The next line below will establish
+ // such a dependency.
+ const run_cmd = b.addRunArtifact(exe);
+
+ // By making the run step depend on the install step, it will be run from the
+ // installation directory rather than directly from within the cache directory.
+ // This is not necessary, however, if the application depends on other installed
+ // files, this ensures they will be present and in the expected location.
+ run_cmd.step.dependOn(b.getInstallStep());
+
+ // This allows the user to pass arguments to the application in the build
+ // command itself, like this: `zig build run -- arg1 arg2 etc`
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ // This creates a build step. It will be visible in the `zig build --help` menu,
+ // and can be selected like this: `zig build run`
+ // This will evaluate the `run` step rather than the default, which is "install".
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ // Creates a step for unit testing. This only builds the test executable
+ // but does not run it.
+ const lib_unit_tests = b.addTest(.{
+ .root_source_file = .{ .path = "src/root.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_source_file = .{ .path = "src/main.zig" },
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ // Similar to creating the run step earlier, this exposes a `test` step to
+ // the `zig build --help` menu, providing a way for the user to request
+ // running the unit tests.
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_lib_unit_tests.step);
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/build.zig.zon b/build.zig.zon
new file mode 100644
index 0000000..e7ef4c1
--- /dev/null
+++ b/build.zig.zon
@@ -0,0 +1,34 @@
+.{
+ .name = "bikeshed",
+ // This is a [Semantic Version](https://semver.org/).
+ // In a future version of Zig it will be used for package deduplication.
+ .version = "0.1.0",
+
+ // This field is optional.
+ // This is currently advisory only; Zig does not yet do anything
+ // with this value.
+ //.minimum_zig_version = "0.11.0",
+
+ // This field is optional.
+ // Each dependency must either provide a `url` and `hash`, or a `path`.
+ // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
+ // Once all dependencies are fetched, `zig build` no longer requires
+ // internet connectivity.
+ .dependencies = .{
+ .pg = .{
+ .url = "https://github.com/karlseguin/pg.zig/archive/22fe3ae98e94f9702858559f129eac988545c250.tar.gz",
+ .hash = "1220a2b9a47808e9493ba5274b3ed17e09f9adc7350b74f23826a27c22e5b13e6425",
+ },
+ .httpz = .{
+ .url = "https://github.com/karlseguin/http.zig/archive/4264712aa049638eb4d221ce3f2f6febf0e522e2.tar.gz",
+ .hash = "12201aa8eabca33d76741aef45d01eed6344588266d43454bbc2d56f52fe0c963b4c",
+ },
+ },
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ //"LICENSE",
+ //"README.md",
+ },
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..809faf4
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,807 @@
+{
+ "nodes": {
+ "cachix": {
+ "inputs": {
+ "devenv": "devenv_2",
+ "flake-compat": "flake-compat_2",
+ "nixpkgs": [
+ "devenv",
+ "nixpkgs"
+ ],
+ "pre-commit-hooks": "pre-commit-hooks"
+ },
+ "locked": {
+ "lastModified": 1710475558,
+ "narHash": "sha256-egKrPCKjy/cE+NqCj4hg2fNX/NwLCf0bRDInraYXDgs=",
+ "owner": "cachix",
+ "repo": "cachix",
+ "rev": "661bbb7f8b55722a0406456b15267b5426a3bda6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "cachix",
+ "type": "github"
+ }
+ },
+ "devenv": {
+ "inputs": {
+ "cachix": "cachix",
+ "flake-compat": "flake-compat_4",
+ "nix": "nix_2",
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "pre-commit-hooks": "pre-commit-hooks_2"
+ },
+ "locked": {
+ "lastModified": 1712300418,
+ "narHash": "sha256-tQKGdBAYIPeLNOtkymFQJh47w3R3e8adfgzVZ76qSeY=",
+ "owner": "cachix",
+ "repo": "devenv",
+ "rev": "8827aa19daf1fc3f53e7adcc7201933ef28f8652",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "devenv",
+ "type": "github"
+ }
+ },
+ "devenv_2": {
+ "inputs": {
+ "flake-compat": [
+ "devenv",
+ "cachix",
+ "flake-compat"
+ ],
+ "nix": "nix",
+ "nixpkgs": "nixpkgs",
+ "poetry2nix": "poetry2nix",
+ "pre-commit-hooks": [
+ "devenv",
+ "cachix",
+ "pre-commit-hooks"
+ ]
+ },
+ "locked": {
+ "lastModified": 1708704632,
+ "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
+ "owner": "cachix",
+ "repo": "devenv",
+ "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "ref": "python-rewrite",
+ "repo": "devenv",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673956053,
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_2": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_3": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_4": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_5": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673956053,
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_6": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673956053,
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-compat_7": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673956053,
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1689068808,
+ "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_2": {
+ "inputs": {
+ "systems": "systems_2"
+ },
+ "locked": {
+ "lastModified": 1701680307,
+ "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_3": {
+ "inputs": {
+ "systems": "systems_3"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_4": {
+ "locked": {
+ "lastModified": 1659877975,
+ "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_5": {
+ "inputs": {
+ "systems": "systems_5"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_6": {
+ "locked": {
+ "lastModified": 1659877975,
+ "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "devenv",
+ "cachix",
+ "pre-commit-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1703887061,
+ "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "gitignore_2": {
+ "inputs": {
+ "nixpkgs": [
+ "devenv",
+ "pre-commit-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "gitignore_3": {
+ "inputs": {
+ "nixpkgs": [
+ "zls",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "langref": {
+ "flake": false,
+ "locked": {
+ "narHash": "sha256-94broSBethRhPJr0G9no4TPyB8ee6BQ/hHK1QnLPln0=",
+ "type": "file",
+ "url": "https://raw.githubusercontent.com/ziglang/zig/54bbc73f8502fe073d385361ddb34a43d12eec39/doc/langref.html.in"
+ },
+ "original": {
+ "type": "file",
+ "url": "https://raw.githubusercontent.com/ziglang/zig/54bbc73f8502fe073d385361ddb34a43d12eec39/doc/langref.html.in"
+ }
+ },
+ "nix": {
+ "inputs": {
+ "flake-compat": "flake-compat",
+ "nixpkgs": [
+ "devenv",
+ "cachix",
+ "devenv",
+ "nixpkgs"
+ ],
+ "nixpkgs-regression": "nixpkgs-regression"
+ },
+ "locked": {
+ "lastModified": 1708577783,
+ "narHash": "sha256-92xq7eXlxIT5zFNccLpjiP7sdQqQI30Gyui2p/PfKZM=",
+ "owner": "domenkozar",
+ "repo": "nix",
+ "rev": "ecd0af0c1f56de32cbad14daa1d82a132bf298f8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "domenkozar",
+ "ref": "devenv-2.21",
+ "repo": "nix",
+ "type": "github"
+ }
+ },
+ "nix-github-actions": {
+ "inputs": {
+ "nixpkgs": [
+ "devenv",
+ "cachix",
+ "devenv",
+ "poetry2nix",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1688870561,
+ "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
+ "owner": "nix-community",
+ "repo": "nix-github-actions",
+ "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "nix-github-actions",
+ "type": "github"
+ }
+ },
+ "nix_2": {
+ "inputs": {
+ "flake-compat": "flake-compat_5",
+ "nixpkgs": [
+ "devenv",
+ "nixpkgs"
+ ],
+ "nixpkgs-regression": "nixpkgs-regression_2"
+ },
+ "locked": {
+ "lastModified": 1710500156,
+ "narHash": "sha256-zvCqeUO2GLOm7jnU23G4EzTZR7eylcJN+HJ5svjmubI=",
+ "owner": "domenkozar",
+ "repo": "nix",
+ "rev": "c5bbf14ecbd692eeabf4184cc8d50f79c2446549",
+ "type": "github"
+ },
+ "original": {
+ "owner": "domenkozar",
+ "ref": "devenv-2.21",
+ "repo": "nix",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1692808169,
+ "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-regression": {
+ "locked": {
+ "lastModified": 1643052045,
+ "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
+ "type": "github"
+ }
+ },
+ "nixpkgs-regression_2": {
+ "locked": {
+ "lastModified": 1643052045,
+ "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
+ "type": "github"
+ }
+ },
+ "nixpkgs-stable": {
+ "locked": {
+ "lastModified": 1704874635,
+ "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-23.11",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs-stable_2": {
+ "locked": {
+ "lastModified": 1710695816,
+ "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "614b4613980a522ba49f0d194531beddbb7220d3",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-23.11",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1710796454,
+ "narHash": "sha256-lQlICw60RhH8sHTDD/tJiiJrlAfNn8FDI9c+7G2F0SE=",
+ "owner": "cachix",
+ "repo": "devenv-nixpkgs",
+ "rev": "06fb0f1c643aee3ae6838dda3b37ef0abc3c763b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "ref": "rolling",
+ "repo": "devenv-nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_3": {
+ "locked": {
+ "lastModified": 1702350026,
+ "narHash": "sha256-A+GNZFZdfl4JdDphYKBJ5Ef1HOiFsP18vQe9mqjmUis=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "9463103069725474698139ab10f17a9d125da859",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-23.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_4": {
+ "locked": {
+ "lastModified": 1711593151,
+ "narHash": "sha256-/9NCoPI7fqJIN8viONsY9X0fAeq8jc3GslFCO0ky6TQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "bb2b73df7bcfbd2dd55ff39b944d70547d53c267",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "poetry2nix": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nix-github-actions": "nix-github-actions",
+ "nixpkgs": [
+ "devenv",
+ "cachix",
+ "devenv",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1692876271,
+ "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
+ "owner": "nix-community",
+ "repo": "poetry2nix",
+ "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "poetry2nix",
+ "type": "github"
+ }
+ },
+ "pre-commit-hooks": {
+ "inputs": {
+ "flake-compat": "flake-compat_3",
+ "flake-utils": "flake-utils_2",
+ "gitignore": "gitignore",
+ "nixpkgs": [
+ "devenv",
+ "cachix",
+ "nixpkgs"
+ ],
+ "nixpkgs-stable": "nixpkgs-stable"
+ },
+ "locked": {
+ "lastModified": 1708018599,
+ "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=",
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "type": "github"
+ }
+ },
+ "pre-commit-hooks_2": {
+ "inputs": {
+ "flake-compat": [
+ "devenv",
+ "flake-compat"
+ ],
+ "flake-utils": "flake-utils_3",
+ "gitignore": "gitignore_2",
+ "nixpkgs": [
+ "devenv",
+ "nixpkgs"
+ ],
+ "nixpkgs-stable": "nixpkgs-stable_2"
+ },
+ "locked": {
+ "lastModified": 1712055707,
+ "narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=",
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "rev": "e35aed5fda3cc79f88ed7f1795021e559582093a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "pre-commit-hooks.nix",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "devenv": "devenv",
+ "nixpkgs": "nixpkgs_2",
+ "systems": "systems_4",
+ "zig": "zig",
+ "zls": "zls"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_2": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_3": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_4": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_5": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "zig": {
+ "inputs": {
+ "flake-compat": "flake-compat_6",
+ "flake-utils": "flake-utils_4",
+ "nixpkgs": "nixpkgs_3"
+ },
+ "locked": {
+ "lastModified": 1712449544,
+ "narHash": "sha256-OI5ymEdUu3jCTG51gvQjT+BwBJq9e3cphYo60fiVlPw=",
+ "owner": "mitchellh",
+ "repo": "zig-overlay",
+ "rev": "8291f24e45117385239489b1252aae3ef64cde6f",
+ "type": "github"
+ },
+ "original": {
+ "owner": "mitchellh",
+ "repo": "zig-overlay",
+ "type": "github"
+ }
+ },
+ "zig-overlay": {
+ "inputs": {
+ "flake-compat": "flake-compat_7",
+ "flake-utils": "flake-utils_6",
+ "nixpkgs": [
+ "zls",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1711627798,
+ "narHash": "sha256-4BUZmgUFrrD5dRZbOUYRRQEDwLX/r7/ErLi+vHfB/+8=",
+ "owner": "mitchellh",
+ "repo": "zig-overlay",
+ "rev": "b01e0b81d1fa489e54362ea0a74f182eaa9a35bb",
+ "type": "github"
+ },
+ "original": {
+ "owner": "mitchellh",
+ "repo": "zig-overlay",
+ "type": "github"
+ }
+ },
+ "zls": {
+ "inputs": {
+ "flake-utils": "flake-utils_5",
+ "gitignore": "gitignore_3",
+ "langref": "langref",
+ "nixpkgs": "nixpkgs_4",
+ "zig-overlay": "zig-overlay"
+ },
+ "locked": {
+ "lastModified": 1711925513,
+ "narHash": "sha256-DFgsGlEGsxLgtRrh7J+v8x4w+/cJatTCkrZP3/0Gb/o=",
+ "owner": "zigtools",
+ "repo": "zls",
+ "rev": "4e01c08f558ea07462aaa7b71d2a24f86f47a855",
+ "type": "github"
+ },
+ "original": {
+ "owner": "zigtools",
+ "repo": "zls",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..96843c7
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,99 @@
+{
+ inputs = {
+ nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
+ systems.url = "github:nix-systems/default";
+ devenv.url = "github:cachix/devenv";
+ devenv.inputs.nixpkgs.follows = "nixpkgs";
+ zig.url = "github:mitchellh/zig-overlay";
+ zls.url = "github:zigtools/zls";
+ };
+
+ nixConfig = {
+ extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
+ extra-substituters = "https://devenv.cachix.org";
+ };
+
+ outputs = { self, nixpkgs, devenv, systems, ... } @ inputs:
+ let
+ overlays = [
+ # Other overlays
+ (final: prev: {
+ zigpkgs = inputs.zig.packages.${prev.system};
+ zls = inputs.zls.packages.${prev.system}.zls;
+ })
+ ];
+
+ systems = builtins.attrNames inputs.zig.packages;
+
+
+ # forEachSystem = nixpkgs.lib.genAttrs (import systems);
+ forEachSystem = nixpkgs.lib.genAttrs (builtins.attrNames inputs.zig.packages);
+ in
+ {
+ packages = forEachSystem (system: {
+ devenv-up = self.devShells.${system}.default.config.procfileScript;
+ });
+
+ devShells = forEachSystem
+ (system:
+ let
+ # pkgs = nixpkgs.legacyPackages.${system};
+ pkgs = import nixpkgs {inherit overlays system; };
+ in
+ {
+ default = devenv.lib.mkShell {
+ inherit inputs pkgs;
+ modules = [
+ (
+ { pkgs, ... }:
+
+ {
+ # https://devenv.sh/packages/
+ packages = [
+ pkgs.perl538Packages.TAPParserSourceHandlerpgTAP
+ pkgs.postgresql16Packages.pgtap
+ pkgs.zigpkgs.master
+ pkgs.zls
+ ];
+
+ # https://devenv.sh/scripts/
+ # scripts.hello.exec = "echo hello from $DATABASE";
+
+ enterShell = ''
+ # hello
+ # git --version
+ '';
+
+ # https://devenv.sh/tests/
+ enterTest = ''
+ echo "Running database tests"
+ pg_prove
+ '';
+
+ # https://devenv.sh/services/
+ services.postgres = {
+ enable = true;
+ package = pkgs.postgresql_16;
+ extensions = extensions: [
+ extensions.pgtap
+ ];
+ initialScript = builtins.readFile ./src/schema.sql;
+ };
+
+ # https://devenv.sh/languages/
+ # languages.nix.enable = true;
+
+ # https://devenv.sh/pre-commit-hooks/
+ # pre-commit.hooks.shellcheck.enable = true;
+
+ # https://devenv.sh/processes/
+ # processes.ping.exec = "ping example.com";
+
+ # See full reference at https://devenv.sh/reference/options/
+ }
+ )
+ ];
+ };
+ });
+ };
+}
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..b4cac0c
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,29 @@
+const pg = @import("pg");
+const std = @import("std");
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ var env_map = try std.process.getEnvMap(allocator);
+ defer env_map.deinit();
+
+ var pool = try pg.Pool.init(allocator, .{ .size = 5, .connect = .{
+ .unix_socket = env_map.get("PGSOCK") orelse ".devenv/run/postgres/.s.PGSQL.5432",
+ .port = 5432,
+ }, .auth = .{
+ .database = env_map.get("PGDATABASE") orelse "bikeshed",
+ .username = env_map.get("PGUSER") orelse "bikeshed_website",
+ .password = env_map.get("PGPASS") orelse "bikeshed",
+ .timeout = 10_000,
+ } });
+ defer pool.deinit();
+
+ var result = try pool.query("SELECT username FROM users", .{});
+ defer result.deinit();
+
+ while (try result.next()) |user| {
+ std.debug.print("Username: {s}\n", .{user.get([]u8, 0)});
+ }
+}
diff --git a/src/root.zig b/src/root.zig
new file mode 100644
index 0000000..ecfeade
--- /dev/null
+++ b/src/root.zig
@@ -0,0 +1,10 @@
+const std = @import("std");
+const testing = std.testing;
+
+export fn add(a: i32, b: i32) i32 {
+ return a + b;
+}
+
+test "basic add functionality" {
+ try testing.expect(add(3, 7) == 10);
+}
diff --git a/src/schema.sql b/src/schema.sql
new file mode 100644
index 0000000..7ebb828
--- /dev/null
+++ b/src/schema.sql
@@ -0,0 +1,78 @@
+\set database_name bikeshed
+\set schema :database_name
+\set owner :database_name
+\set rw_name :database_name '_website'
+\set ro_name :database_name '_ro'
+
+CREATE EXTENSION IF NOT EXISTS pgtap;
+
+DROP ROLE IF EXISTS ddl;
+CREATE ROLE ddl WITH NOLOGIN;
+
+DROP ROLE IF EXISTS dml;
+CREATE ROLE dml WITH NOLOGIN;
+
+DROP ROLE IF EXISTS read_only;
+CREATE ROLE read_only WITH NOLOGIN;
+
+DROP SCHEMA IF EXISTS :schema CASCADE;
+DROP DATABASE IF EXISTS :database_name;
+
+DROP ROLE IF EXISTS :owner;
+CREATE ROLE :owner WITH LOGIN PASSWORD :'owner';
+GRANT ddl TO :owner;
+
+DROP ROLE IF EXISTS :rw_name;
+CREATE ROLE :rw_name WITH LOGIN PASSWORD :'rw_name';
+GRANT dml TO :rw_name;
+
+DROP ROLE IF EXISTS :ro_name;
+CREATE ROLE :ro_name WITH LOGIN PASSWORD :'ro_name';
+GRANT read_only TO :ro_name;
+
+ALTER ROLE :owner SET search_path TO :schema, public;
+ALTER ROLE :rw_name SET search_path TO :schema, public;
+ALTER ROLE :ro_name SET search_path TO :schema, public;
+
+CREATE DATABASE :database_name OWNER :owner;
+
+REVOKE CREATE ON SCHEMA public FROM PUBLIC;
+REVOKE ALL ON DATABASE :database_name FROM PUBLIC;
+
+GRANT CONNECT, TEMPORARY ON DATABASE :database_name TO ddl, dml, read_only;
+
+\c :database_name
+SET ROLE :owner;
+
+CREATE SCHEMA :schema AUTHORIZATION :schema;
+
+GRANT USAGE, CREATE ON SCHEMA :schema TO ddl;
+GRANT USAGE ON SCHEMA :schema TO dml, read_only;
+
+REVOKE ALL ON DATABASE :database_name FROM PUBLIC;
+
+GRANT CONNECT, TEMPORARY ON DATABASE :database_name TO ddl, dml, read_only;
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA :schema
+GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO dml;
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA :schema
+GRANT SELECT ON TABLES TO read_only;
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA :schema
+GRANT USAGE, SELECT ON SEQUENCES TO dml, read_only;
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA :schema
+GRANT UPDATE ON SEQUENCES TO dml;
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA :schema
+GRANT EXECUTE ON ROUTINES TO dml, read_only;
+
+CREATE TABLE :schema.users (
+ user_id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
+ username text,
+ created_at timestamptz NOT NULL DEFAULT now(),
+ modified_at timestamptz NOT NULL DEFAULT now(),
+
+ CONSTRAINT users_pk PRIMARY KEY (user_id)
+);
diff --git a/test/00-permissions.sql b/test/00-permissions.sql
new file mode 100644
index 0000000..1a7a8e0
--- /dev/null
+++ b/test/00-permissions.sql
@@ -0,0 +1,25 @@
+CREATE EXTENSION IF NOT EXISTS pgtap;
+BEGIN;
+SELECT plan(9);
+
+SELECT has_group('dml');
+SELECT has_group('ddl');
+SELECT has_group('read_only');
+
+SELECT has_user('bikeshed');
+SELECT database_privs_are(
+ 'bikeshed', 'bikeshed', ARRAY['CONNECT', 'TEMPORARY', 'CREATE']
+);
+
+SELECT has_user('bikeshed_website');
+SELECT database_privs_are(
+ 'bikeshed', 'bikeshed_website', ARRAY['CONNECT', 'TEMPORARY']
+);
+
+SELECT has_user('bikeshed_ro');
+SELECT database_privs_are(
+ 'bikeshed', 'bikeshed_ro', ARRAY['CONNECT', 'TEMPORARY']
+);
+
+SELECT * FROM finish();
+ROLLBACK;
diff --git a/test/01-users.sql b/test/01-users.sql
new file mode 100644
index 0000000..a8573a2
--- /dev/null
+++ b/test/01-users.sql
@@ -0,0 +1,14 @@
+BEGIN;
+SELECT plan(4);
+
+SELECT has_table('users');
+
+SELECT columns_are('users', ARRAY[ 'user_id', 'username', 'modified_at', 'created_at']);
+
+SELECT has_sequence('users_user_id_seq');
+
+SELECT has_index('bikeshed', 'users', 'users_pk', 'user_id');
+
+SELECT * FROM finish();
+ROLLBACK;
+