This commit is contained in:
Fabian Montero 2026-02-03 15:12:59 -06:00
commit b4974a7fc6
9 changed files with 381 additions and 29 deletions

View file

@ -13,39 +13,56 @@
namespace ? doctrine.namespace, namespace ? doctrine.namespace,
passthru ? {}, passthru ? {},
}: let }: let
optionsSet = import options (passthru patchArgs = args: args // passthru // {inherit cfg name doctrine;};
// {
inherit config lib pkgs cfg name doctrine;
});
configSet = import configFiles.${namespace} (passthru optionsSet = args: import options (patchArgs args);
// { configSet = args: import configFiles.${namespace} (patchArgs args);
inherit config lib pkgs doctrine cfg;
});
configFiles = lib.filterAttrs (k: v: v != null) { configFiles = lib.filterAttrs (k: v: v != null) {
inherit sys hm; inherit sys hm;
}; };
cfg = config.${prefix}.${name}; cfg = config.${prefix}.${name};
hasConfig = configFiles ? ${namespace};
in { in {
config = imports = [
lib.optionalAttrs (configFiles ? ${namespace}) (args @ {
(lib.mkIf cfg.enable (lib.mkMerge [ config,
configSet modulesPath ? null,
{ lib,
assertions = pkgs,
map (dependency: { ...
assertion = cfg.enable -> config.${prefix}.${dependency}.enable; }:
message = "${prefix}.${name}.enable requires ${prefix}.${dependency}.enable"; lib.optionalAttrs hasConfig {
}) config = lib.mkIf cfg.enable (lib.mkMerge [
requires; (configSet args)
} {
])); assertions =
map (dependency: {
assertion = cfg.enable -> config.${prefix}.${dependency}.enable;
message = "${prefix}.${name}.enable requires ${prefix}.${dependency}.enable";
})
requires;
}
]);
})
options.${prefix}.${name} = (args @ {
lib.optionalAttrs (options != null && optionsSet ? ${namespace}) optionsSet.${namespace} config,
// { modulesPath ? null,
enable = lib.mkEnableOption name; lib,
}; pkgs,
...
}: let
hasOptions = options != null && optionsForArgs ? ${namespace};
optionsForArgs = optionsSet args;
in
lib.optionalAttrs (hasOptions || hasConfig) {
options.${prefix}.${name} =
lib.optionalAttrs hasOptions optionsForArgs.${namespace}
// {
enable = lib.mkEnableOption name;
};
})
];
} }

View file

@ -0,0 +1,11 @@
{
config,
doctrine,
...
}:
doctrine.lib.mkModule {
inherit config;
name = "socialpredict";
options = ./options.nix;
sys = ./sys.nix;
}

View file

@ -0,0 +1,78 @@
{
config,
doctrine,
lib,
modulesPath,
pkgs,
...
}:
with lib.types; let
inherit (pkgs.${doctrine.prefix}) socialpredict;
in {
sys = {
frontend = lib.mkOption {
type = package;
default = socialpredict.frontend;
defaultText = "pkgs.\${doctrine.prefix}.frontend";
description = "socialpredict frontend package";
};
backend = lib.mkOption {
type = package;
default = socialpredict.backend;
defaultText = "pkgs.\${doctrine.prefix}.backend";
description = "socialpredict backend package";
};
package = lib.mkOption {
type = package;
default = pkgs.${doctrine.prefix}.socialpredict;
defaultText = "pkgs.\${doctrine.prefix}.socialpredict";
description = "socialpredict package";
};
database = lib.mkOption {
type = str;
default = "socialpredict";
description = "database name";
};
user = lib.mkOption {
type = str;
default = "socialpredict";
description = "user that will run the backend";
};
group = lib.mkOption {
type = str;
default = "socialpredict";
description = "group that will run the backend";
};
backendPort = lib.mkOption {
type = port;
description = "backend port";
};
initialAdminPassword = lib.mkOption {
type = str;
default = "change-me";
description = "initial password of the 'admin' user";
};
domain = lib.mkOption {
type = nullOr str;
default = null;
description = "domain host";
};
nginx = lib.mkOption {
type = submodule (
lib.recursiveUpdate (import "${modulesPath}/services/web-servers/nginx/vhost-options.nix" {inherit config lib;}) {}
);
default = {};
description = "extra nginx virtual host config";
};
};
}

View file

@ -0,0 +1,102 @@
{
cfg,
doctrine,
lib,
pkgs,
...
}: {
services = {
nginx = lib.mkIf (cfg.domain != null) {
enable = true;
virtualHosts.${cfg.domain} = lib.mkMerge [
cfg.nginx
{
locations = {
"/" = {
root = "${cfg.frontend}";
index = "index.html";
tryFiles = "$uri $uri/ /index.html =404";
};
"/api/" = {
proxyPass = "http://localhost:${toString cfg.backendPort}/";
};
"= /env-config.js" = {
alias = "${pkgs.writeText "socialpredict-env-config.js" ''
window.__ENV__ = {
DOMAIN_URL: "https://${cfg.domain}",
API_URL: "https://${cfg.domain}/api"
};
''}";
};
};
}
];
};
postgresql = {
enable = true;
ensureUsers = [
{
name = cfg.user;
ensureDBOwnership = cfg.user == cfg.database;
}
];
ensureDatabases = [cfg.database];
};
};
systemd.services.socialpredict = {
after = ["postgresql.service"];
wants = ["postgresql.service"];
wantedBy = ["multi-user.target"];
environment = {
ADMIN_PASSWORD = cfg.initialAdminPassword;
BACKEND_PORT = toString cfg.backendPort;
POSTGRES_URL = "postgresql:///${cfg.database}?host=/var/run/postgresql";
};
serviceConfig = {
Group = cfg.group;
User = cfg.user;
ExecStart = lib.getExe cfg.backend;
KeyringMode = "private";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateMounts = "yes";
PrivateTmp = "yes";
ProtectControlGroups = true;
ProtectHome = "yes";
ProtectHostname = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
ReadWritePaths = [
"/var/run/postgresql"
];
};
};
users = {
groups.${cfg.group} = {};
users.${cfg.user} = {
inherit (cfg) group;
isSystemUser = true;
};
};
}

View file

@ -4,7 +4,7 @@
makeWrapper, makeWrapper,
maven, maven,
openjdk, openjdk,
wrapGAppsHook, wrapGAppsHook3,
libasep11 ? null, libasep11 ? null,
}: let }: let
jdk = openjdk.override { jdk = openjdk.override {
@ -27,11 +27,11 @@ in
./0001-Remove-CheckUpdatePlugin-from-default-list.patch ./0001-Remove-CheckUpdatePlugin-from-default-list.patch
]; ];
mvnHash = "sha256-h1zoStTgaE7toWWKq0Y0ahOORyltChwjmaMYjLgs1VE="; mvnHash = "sha256-m3UaOLNyIlVAOI5tzxMlxg4KZ1N5gT2O2WSka+jBat4=";
nativeBuildInputs = [ nativeBuildInputs = [
makeWrapper makeWrapper
wrapGAppsHook wrapGAppsHook3
]; ];
postPatch = lib.optionalString (libasep11 != null) '' postPatch = lib.optionalString (libasep11 != null) ''

View file

@ -6,5 +6,6 @@ in {
athena-bccr = callPackage ./athena-bccr {}; athena-bccr = callPackage ./athena-bccr {};
snapborg = final.python3Packages.callPackage ./snapborg {}; snapborg = final.python3Packages.callPackage ./snapborg {};
socialpredict = callPackage ./socialpredict {};
spliit = callPackage ./spliit {}; spliit = callPackage ./spliit {};
} }

View file

@ -0,0 +1,30 @@
From 67cf25f7cb397d37d807797b6013447b19c8f73b Mon Sep 17 00:00:00 2001
From: Alejandro Soto <alejandro@34project.org>
Date: Thu, 29 Jan 2026 23:21:21 -0600
Subject: [PATCH] Support POSTGRES_URL for overriding the PG DSN
---
backend/util/postgres.go | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/util/postgres.go b/util/postgres.go
index ac69932..030f516 100644
--- a/util/postgres.go
+++ b/util/postgres.go
@@ -49,8 +49,11 @@ func InitDB() {
dbPort = "5432"
}
- dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC",
- dbHost, dbUser, dbPassword, dbName, dbPort)
+ dsn := os.Getenv("POSTGRES_URL")
+ if dsn == "" {
+ dsn = fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC",
+ dbHost, dbUser, dbPassword, dbName, dbPort)
+ }
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
--
2.51.2

View file

@ -0,0 +1,47 @@
From 9ce9d4d3bdbd12dfb3dda98f3d4f72d3bbe724b7 Mon Sep 17 00:00:00 2001
From: Alejandro Soto <alejandro@34project.org>
Date: Sat, 31 Jan 2026 13:04:28 -0600
Subject: [PATCH 2/2] Fix ${API_URL}/api/v0/... -> ${API_URL}/v0/...
---
src/hooks/usePortfolio.jsx | 2 +-
src/hooks/useUserData.jsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/hooks/usePortfolio.jsx b/src/hooks/usePortfolio.jsx
index 0f0774b..a233ea3 100644
--- a/src/hooks/usePortfolio.jsx
+++ b/src/hooks/usePortfolio.jsx
@@ -17,7 +17,7 @@ const usePortfolio = (username) => {
headers['Content-Type'] = 'application/json';
}
- const response = await fetch(`${API_URL}/api/v0/portfolio/${username}`, { headers });
+ const response = await fetch(`${API_URL}/v0/portfolio/${username}`, { headers });
if (!response.ok) {
throw new Error('Failed to fetch portfolio');
}
diff --git a/src/hooks/useUserData.jsx b/src/hooks/useUserData.jsx
index 593594f..960c5aa 100644
--- a/src/hooks/useUserData.jsx
+++ b/src/hooks/useUserData.jsx
@@ -15,14 +15,14 @@ const useUserData = (username, usePrivateProfile = false) => {
if (usePrivateProfile) {
// Use private profile endpoint for authenticated user's own profile
- url = `${API_URL}/api/v0/privateprofile`;
+ url = `${API_URL}/v0/privateprofile`;
headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
} else {
// Use public user endpoint for viewing other users' profiles
- url = `${API_URL}/api/v0/userinfo/${username}`;
+ url = `${API_URL}/v0/userinfo/${username}`;
if (token) {
headers = {
'Authorization': `Bearer ${token}`,
--
2.51.2

View file

@ -0,0 +1,66 @@
{
buildGoModule,
buildNpmPackage,
fetchFromGitHub,
lib,
}: let
version = "2.1.0";
src = fetchFromGitHub {
owner = "openpredictionmarkets";
repo = "socialpredict";
tag = "v${version}";
hash = "sha256-aV6Z7vsqV8zxyB+v7hSyOm/jzGqR8YnhG+xLKSC9Qoo=";
};
meta = {
description = "Easy to Deploy Prediction Market Platform ";
homepage = "https://github.com/openpredictionmarkets/socialpredict";
license = lib.licenses.mit;
maintainers = with lib.maintainers; []; # [ _3442 ];
};
in {
backend = buildGoModule {
pname = "socialpredict-backend";
inherit src version;
patches = [
./0001-Support-POSTGRES_URL-for-overriding-the-PG-DSN.patch
];
sourceRoot = "source/backend";
vendorHash = "sha256-ah2d+gHe7HULEsqMSUwGOL4D00aY0QtZvcD3pTQp/Q0=";
meta = meta // {mainProgram = "socialpredict";};
};
frontend = buildNpmPackage {
pname = "socialpredict-frontend";
inherit src meta version;
sourceRoot = "source/frontend";
npmDepsHash = "sha256-zn1yPtvi8DaKESMGAtqnh/66xET+QaCa1TUlpbatI70=";
patches = [
./0002-Fix-API_URL-api-v0-.-API_URL-v0.patch
];
buildPhase = ''
runHook preBuild
node --max_old_space_size=1024000 ./node_modules/vite/bin/vite.js build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r build/* $out
rm $out/env-config.js.template
runHook postInstall
'';
};
}