Serve your site and sell courses with self hosted server.
sserver is simple headless server for hosting courses and associated blog/static content from private github repository with minimal overhead.
It provides https out of the box so you don't have to deal with installing/managing certificates. It syncs the content automatically from github so you don't have to upload your content to server. It also supports premium content with simple configuration file without affecting your content workflow for e.g. if you are using static site generator like hugo you can hide the premium content from public by specifying it in config file. It has stripe integration so you can sell your premium content. It has user management built in for adding/authenticating users. It also has admin api so you can monitor orders/users of your site.
Here are some examples of how it can be used, please make sure to set SS_<> environment variables before trying them out e.g.
export SS_GITHUB_TOKEN=<github_token>
export SS_STRIPE_TOKEN=<stripe_token>
export SS_SMTP_FROM=<email_id>
export SS_SMTP_USER=<smtp_username>
export SS_SMTP_PWD=<smtp_password>
export SS_SMTP_HOST=<smtp_host_address>
export SS_SMTP_PORT=<smtp_port>
export SS_ADMIN_EMAIL=<admin_email>
export SS_ADMIN_PWD=<admin_password_for_sserver>
./sserver -repo "https://github.com/newbeelearn/sserver.git"
Repository should have index.html and ssconfig.toml in its root directory. If hugo/jekyll etc. like static site generator is used, repository should contain generated site.(It would have index.html in root by default)
./sserver -repo "https://github.com/newbeelearn/sserver.git?folder=public"
Repository should have index.html in folder from where you want to serve the content, typically it is public if hugo/jekyll etc. static site generators are used. It should have ssconfig.toml in the root directory
./sserver -repo "https://github.com/newbeelearn/sserver.git?ref=test-config"
Branch should have index.html and ssconfig.toml in its root directory. If hugo/jekyll etc. like static site generator is used, branch should contain generated site.(It would have index.html in root by default)
./sserver -repo "https://github.com/newbeelearn/sserver.git?domain=example.com"
Repository should have index.html and ssconfig.toml in its root directory Access to the domain from which site is served sserver should have permissions to bind to 443 port, this can be done with following command
sudo setcap 'cap_net_bind_service=+ep' sserver
./sserver -repo "file:///workspace/projects/newbeelearn.com/sserver"
Repository should have index.html and ssconfig.toml in its root directory. All the options i.e. folder/domain etc. can be specified in case of local files as well
Sample ssconfig.toml can be found below
# specify the site
[site]
# period to check for new content
syncinterval = "@every 12h"
# product/course details
[[site.prod]]
name = "course1"
# path from root, this will be accessible to users who have bought the course
path = "courses/course1"
# can be draft/active, buying functionality will be enabled when status is active
status = "active"
# unique identifier for the course
sku = "prod-course-1"
# price in cents
price = 10000
# currency
currency = "USD"
sserver creates "wwwss" directory from where it is run
drwxrwxr-x 2 test test 4096 Nov 30 17:04 a
drwxrwxr-x 8 test test 4096 Nov 30 17:04 b
drwxrwxr-x 2 test test 4096 Nov 30 17:04 certs
drwxrwxr-x 2 test test 4096 Nov 30 17:04 logs
-rw-rw-r-- 1 test test 527483 Nov 30 17:04 tmp.zip
-rw-rw-r-- 1 test test 49152 Nov 30 17:05 ssapp.db
Configuration file is used to specify the products/courses that you want to sell as well as some server parameters like how often the site should be synced etc.
Sample ssconfig.toml file is shown below
#specify the site
[site]
#period to check for new content default is 12 hours
syncinterval = "@every 12h"
[[site.prod]]
name = "course1"
path = "courses/course1"
status = "active"
sku = "prod-course-1"
price = 10000
currency = "USD"
All api endpoints are relative to the domain used for serving the content
i.e. if domain is example.com and api is /api/v1/product/list
request would be https://example.com/api/v1/product/list
Role hierarchy is like this admin > user > guest Any api accessible to guest is also accessible to user and any api accessible to user is also accessible to admin For accessing user/admin api's session cookie obtained after logging in must be supplied with each request in practice this will be taken care by the browser
Description | Request | Role |
---|---|---|
Register User | POST /api/v1/user/register |
guest |
Login User | POST /api/v1/user/login |
guest |
Logout user | GET /api/v1/user/logout |
guest |
Verify user | GET /api/v1/user/verify/:id |
guest |
Reset user password | POST /api/v1/user/reset |
guest |
Get product list | GET /api/v1/product/list |
guest |
Create order | POST /api/v1/order/id |
guest |
Modify order | PUT /api/v1/order/id |
user |
Checkout order | POST /api/v1/order/checkout |
user |
Get order by order id | GET /api/v1/order/id/:id |
user |
Get all orders by user | GET /api/v1/order/id/list |
user |
Change user password | POST /api/v1/user/changepwd |
user |
Get all orders | GET /api/v1/order/list |
admin |
Get all users | GET /api/v1/user/list |
admin |
Register new user with email and password. Sends mail to the email used to register with verification code if SMTP server configuration is set.
Example Request
curl 'http://localhost:54545/api/v1/user/register'
-H 'Content-Type: application/x-www-form-urlencoded'
-X POST
--data-raw 'email=stripe%40newbeelearn.com&password=test&confirm-password=test&remember=on'
Example Response
{"status":"success"}
Login user with email and password. Returns json if "postlogin" field is not set in config file otherwise redirects to the page specified in "postlogin"
Example Request
curl 'http://localhost:54545/api/v1/user/login'
-H 'Content-Type: application/x-www-form-urlencoded'
-X POST
--data-raw 'email=admin%40example.com&password=admin'
Example Response
{
"data": {
"user_id": "1",
"username": ""
},
"msg": "user found",
"status": "success"
}
Logs out logged in user and redirects to homepage
Example Request
curl 'http://localhost:54545/api/v1/user/logout'
-H 'Cookie: session_id=9e8b22a3-15ac-442f-bf65-15c37dbfc889; max-age=300; path=/; secure; SameSite=Lax'
Example Response
<!doctype html>
<html lang="en">
<head>
</head>
<body>
</body>
</html>
Verifies the email of registered user by sending url if domain is set or code that should be appended after the verify api if domain is not set
Example Request
curl 'http://localhost:54545/api/v1/user/verify/cafj5grn0gpog1j3a0m0'
Example Response
{"status":"success"}
Resets the user password and sends the new temporary code for login. User needs to use this code on next login and change the password
Example Request
curl 'http://localhost:54545/api/v1/user/reset'
-H 'Content-Type: application/x-www-form-urlencoded'
-X POST
--data-raw 'email=stripe%40newbeelearn.com'
Example Response
{
"data": null,
"msg": "password reset successful",
"status": "success"
}
Get all the products listed for sale on website. Takes "limit" and "offset" as queries. If query is not set default values of limit is set to 10 and offset to 0
Example Request
curl 'http://localhost:54545/api/v1/product/list?limit=1&offset=0'
Example Response
{
"data": [
{
"prd_id": 1,
"prd_name": "course1",
"sku": "prod-course-1",
"permalink": "users/list/",
"price": 10000,
"currency": "USD",
"period": 365,
"status": "active"
}
],
"msg": "Order found",
"status": "success"
}
Creates new order with products listed in "line_item"
field. Request should be valid json.
Actual order_id/user_id
etc. fields are populated by the server any dummy value can be passed.
Example Request
curl 'http://localhost:54545/api/v1/order/id'
-H 'Content-Type: application/json; charset=utf-8'
-H 'Cookie: session_id=cad8439e-dcc4-475e-94fc-12b75f85bb20; max-age=300; path=/; secure; SameSite=Lax'
-X POST
--data-raw ' {
"order_id": 1,
"user_id": 3,
"currency": "USD",
"line_items": [
{
"sku": "prod-course-1"
},
{
"sku": "prod-course-3"
}
]
}'
Example Response
{
"data": {
"order_id": 1,
"user_id": 3,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"status": "active",
"currency": "USD",
"order_number": "cafjktbn0gpp5hq3dt4g",
"grand_total": 11000,
"line_items": [
{
"line_id": 1,
"order_id": 1,
"prd_id": 1,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"grand": 10000,
"enabled": true,
"sku": "prod-course-1"
},
{
"line_id": 2,
"order_id": 1,
"prd_id": 2,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"grand": 1000,
"enabled": true,
"sku": "prod-course-3"
}
]
},
"msg": "Order found",
"status": "success"
}
Modifies existing order by adding/deleting products in line_item
field. User must be logged in to
modify the order and order should be in active state. Use the previous response of create order or get order
to add/delete products
Example Request
curl 'http://localhost:54545/api/v1/order/id'
-H 'Content-Type: application/json; charset=utf-8'
-H 'Cookie: session_id=cad8439e-dcc4-475e-94fc-12b75f85bb20; max-age=300; path=/; secure; SameSite=Lax'
-X PUT
--data-raw ' {
"order_id": 1,
"user_id": 3,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"status": "active",
"currency": "USD",
"order_number": "cafjktbn0gpp5hq3dt4g",
"grand_total": 11000,
"line_items": [
{
"line_id": 2,
"order_id": 1,
"prd_id": 2,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"grand": 1000,
"enabled": true,
"sku": "prod-course-3"
}
]
}'
Example Response
{
"data": {
"order_id": 1,
"user_id": 3,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:48:05.425488765 +0000 UTC",
"status": "active",
"currency": "USD",
"order_number": "cafjktbn0gpp5hq3dt4g",
"grand_total": 1000,
"line_items": [
{
"line_id": 2,
"order_id": 1,
"prd_id": 2,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:48:05.425488765 +0000 UTC",
"grand": 1000,
"enabled": true,
"sku": "prod-course-3"
}
]
},
"msg": "Order found",
"status": "success"
}
Gets stripe url for payment of order created by create order api. Use the response from create order/modify order/get order api to send the request. Do not modify the response in this request it will result in failure.
Example Request
curl 'http://localhost:54545/api/v1/order/checkout'
-H 'Content-Type: application/json; charset=utf-8'
-H 'Cookie: session_id=2f1be070-7256-4e84-a4ef-c14754cabcdb; max-age=300; path=/; secure; SameSite=Lax'
-X POST
--data-raw ' {
"order_id": 1,
"user_id": 3,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"status": "active",
"currency": "USD",
"order_number": "cafjktbn0gpp5hq3dt4g",
"grand_total": 11000,
"line_items": [
{
"line_id": 2,
"order_id": 1,
"prd_id": 2,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"grand": 1000,
"enabled": true,
"sku": "prod-course-3"
}
]
}'
Example Response
{
"data": {
"url": "https://checkout.stripe.com/pay/cs_test_a17D2l74NsKMv29YJ1c5rSBPx7BGSsNAsObGAsOanEJqyFNXKEYDLji4BZ#fidkdWxOYHwnPyd1blpxYHZxWjA0TlVKPHNMaW9vYEd1YmhdUWQ3UUJqSEpMYTMza11ObGAyXDFPcXA8bz1yY1VicVZVdDN8c1NkaUZEazxIQWdjM04wdz1DTmF3PXxHaVE9bTVuZz1pUWw3NTUybHZLZldgaicpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl"
},
"msg": "Order found",
"status": "success"
}
Get order details by order number
Example Request
curl 'http://localhost:54545/api/v1/order/id/cafjktbn0gpp5hq3dt4g'
-H 'Content-Type: application/json; charset=utf-8'
-H 'Cookie: session_id=fe9b9fff-c5c0-4745-becf-ecb0e5abca81; max-age=300; path=/; secure; SameSite=Lax'
Example Response
{
"data": {
"order_id": 1,
"user_id": 3,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:48:05.425488765 +0000 UTC",
"status": "active",
"currency": "USD",
"order_number": "cafjktbn0gpp5hq3dt4g",
"grand_total": 1000,
"line_items": [
{
"line_id": 2,
"order_id": 1,
"prd_id": 2,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:48:05.425488765 +0000 UTC",
"grand": 1000,
"enabled": true
}
]
},
"msg": "Order found",
"status": "success"
}
Get all orders by user. Takes limit and offset as query parameters default values are 10 and 0 respectively
Example Request
curl 'http://localhost:54545/api/v1/order/id/list?limit=1&offset=0'
-H 'Content-Type: application/json; charset=utf-8'
-H 'Cookie: session_id=fe9b9fff-c5c0-4745-becf-ecb0e5abca81; max-age=300; path=/; secure; SameSite=Lax'
Example Response
{
"data": [
{
"order_id": 1,
"user_id": 3,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:48:05.425488765 +0000 UTC",
"status": "active",
"currency": "USD",
"order_number": "cafjktbn0gpp5hq3dt4g",
"grand_total": 1000
}
],
"msg": "Order found",
"status": "success"
}
Change user password. User must be logged in to make this request
Example Request
curl 'http://localhost:54545/api/v1/user/changepwd'
-H 'Content-Type: application/x-www-form-urlencoded'
-H 'Cookie: session_id=fe9b9fff-c5c0-4745-becf-ecb0e5abca81; max-age=300; path=/; secure; SameSite=Lax'
-X POST
--data-raw 'oldpassword=cafjjjbn0gpp5hq3dt40&password=test123'
Example Response
{
"data": null,
"msg": "password change successful",
"status": "success"
}
Get all orders created in the store. Takes limit and offset as query parameters default values are 10 and 0 respectively Available to admin users only. Get all orders
Example Request
curl 'http://localhost:54545/api/v1/order/list?limit=1&offset=0'
-H 'Content-Type: application/json; charset=utf-8'
-H 'Cookie: session_id=e4ecd3a4-b8be-493e-a33d-518ab11c65e8; max-age=300; path=/; secure; SameSite=Lax'
Example Response
{
"data": [
{
"order_id": 1,
"user_id": 3,
"created_at": "2022-06-07 11:45:57.601996759 +0000 UTC",
"modified_at": "2022-06-07 11:48:05.425488765 +0000 UTC",
"status": "active",
"currency": "USD",
"order_number": "cafjktbn0gpp5hq3dt4g",
"price_total": 1000,
"discount_total": 0,
"sub_total": 1000,
"taxes_total": 0,
"grand_total": 1000,
"refunds_total": 0,
"created_channel": "",
"payment_provider": ""
}
],
"msg": "Order found",
"status": "success"
}
Get all users registered on the website. Takes limit and offset as query parameters default values are 10 and 0 respectively Available to admin users only.
Example Request
curl 'http://localhost:54545/api/v1/user/list?limit=1&offset=0'
-H 'Content-Type: application/json; charset=utf-8'
-H 'Cookie: session_id=e4ecd3a4-b8be-493e-a33d-518ab11c65e8; max-age=300; path=/; secure; SameSite=Lax'
Example Response
{
"data": [
{
"user_id": 1,
"email": "[email protected]",
"created_at": "2022-06-07 10:53:00.480128762 +0000 UTC",
"username": "",
"last_password_set": "2022-06-07 10:53:00.480128762 +0000 UTC",
"last_login": "2022-06-07 10:53:00.480128762 +0000 UTC",
"verified": 0,
"reset": 0,
"user_role": "admin"
},
{
"user_id": 2,
"email": "[email protected]",
"created_at": "2022-06-07 10:53:00.532691788 +0000 UTC",
"username": "",
"last_password_set": "2022-06-07 10:53:00.532691788 +0000 UTC",
"last_login": "2022-06-07 10:53:00.532691788 +0000 UTC",
"verified": 0,
"reset": 0,
"user_role": "guest"
},
{
"user_id": 3,
"email": "[email protected]",
"created_at": "2022-06-07 11:13:06.947313364 +0000 UTC",
"username": "",
"last_password_set": "2022-06-07 11:13:06.947313364 +0000 UTC",
"last_login": "2022-06-07 11:13:06.947313364 +0000 UTC",
"verified": 2,
"reset": 1,
"user_role": "user"
}
],
"msg": "Order found",
"status": "success"
}
No, only binaries are released and site is used for discussions around the product.
It's in alpha stage functionality is complete however it may contain bugs
This was created because of the need to host courses
Not right now because subscription is not supported it is for one time digital products only. It also doesn't have route forwarding functionality where your own saas can be plugged in. However these changes can be added if there is sufficient interest plase start discussion if you would like to have these features as of now it is not on roadmap.
It can be used for hosting course and associated blogs. Blogs with newsletter. Blogs with premium content. Landing page of startup and associated blog. Selling themes etc.
linux and macos are supported out of the box. Windows users can use WSL however it is not tested.
Create issue and tag it with feature
This is not yet decided as of now it is free to use. Paid product if available will use separate channel. so if you are downloading from github release it is free forever. Help us in deciding it, tell us what you would pay for it in discussion board.