started db-rewrite. using neo4j and a restful go api to handle DB queries/statements.
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
module the.hub/m
|
||||||
|
|
||||||
|
go 1.24.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.0 h1:chDT68PHNa8JZRmjSkGzAbk1weLWo4rMtDvccvpobg0=
|
||||||
|
github.com/neo4j/neo4j-go-driver/v5 v5.28.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
// "encoding/json"
|
||||||
|
// "fmt"
|
||||||
|
//"io"
|
||||||
|
// "log"
|
||||||
|
"net/http"
|
||||||
|
// "os"
|
||||||
|
// "os/signal"
|
||||||
|
//"strings"
|
||||||
|
// "sync"
|
||||||
|
// "time"
|
||||||
|
// "github.com/gorilla/mux"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
)
|
||||||
|
|
||||||
|
func create_new_post(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_all_posts(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_post(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update_post(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete_post(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
// "encoding/json"
|
||||||
|
"fmt"
|
||||||
|
//"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
//"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
)
|
||||||
|
|
||||||
|
func send_error(w http.ResponseWriter, json []byte) {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintf(w, string(json))
|
||||||
|
}
|
||||||
|
|
||||||
|
func send_response(w http.ResponseWriter, json []byte) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprintf(w, string(json))
|
||||||
|
}
|
||||||
|
|
||||||
|
func serve() {
|
||||||
|
// TODO: Authenticate user credentials to verify they are allowed to even access
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
driver_ctx := context.Background()
|
||||||
|
driver, err := neo4j.NewDriverWithContext("bolt://localhost:7687", neo4j.BasicAuth("neo4j", "jzLbsy2C23WHzQ-", ""))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer driver.Close(driver_ctx)
|
||||||
|
|
||||||
|
err = driver.VerifyConnectivity(driver_ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("neo4j connection established.")
|
||||||
|
|
||||||
|
rsrc_endpoints := []string{
|
||||||
|
"/users",
|
||||||
|
"/users",
|
||||||
|
"/users/{user_id}",
|
||||||
|
"/users/{user_id}",
|
||||||
|
"/users/{user_id}",
|
||||||
|
"/users/{user_id}",
|
||||||
|
"/users/{user_id}/posts",
|
||||||
|
"/users/{user_id}/spaces",
|
||||||
|
"/spaces",
|
||||||
|
"/spaces",
|
||||||
|
"/spaces/{space_id}",
|
||||||
|
"/spaces/{space_id}",
|
||||||
|
"/spaces/{space_id}",
|
||||||
|
"/spaces/{space_id}",
|
||||||
|
"/spaces/{space_id}/users",
|
||||||
|
"/spaces/{space_id}/posts",
|
||||||
|
"/posts",
|
||||||
|
"/posts",
|
||||||
|
"/posts/{post_id}",
|
||||||
|
"/posts/{post_id}",
|
||||||
|
"/posts/{post_id}",
|
||||||
|
}
|
||||||
|
|
||||||
|
methods := []string{
|
||||||
|
http.MethodPost,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodPost,
|
||||||
|
http.MethodPatch,
|
||||||
|
http.MethodDelete,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodPost,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodPost,
|
||||||
|
http.MethodPatch,
|
||||||
|
http.MethodDelete,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodPost,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodPatch,
|
||||||
|
http.MethodDelete,
|
||||||
|
}
|
||||||
|
|
||||||
|
functions := []func(http.ResponseWriter, *http.Request){
|
||||||
|
create_new_user(driver, driver_ctx),
|
||||||
|
retrieve_all_users(driver, driver_ctx),
|
||||||
|
retrieve_user(driver, driver_ctx),
|
||||||
|
auth_user(driver, driver_ctx),
|
||||||
|
update_user(driver, driver_ctx),
|
||||||
|
delete_user(driver, driver_ctx),
|
||||||
|
retrieve_users_posts(driver, driver_ctx),
|
||||||
|
retrieve_users_spaces(driver, driver_ctx),
|
||||||
|
create_new_space(driver, driver_ctx),
|
||||||
|
retrieve_all_spaces(driver, driver_ctx),
|
||||||
|
retrieve_space(driver, driver_ctx),
|
||||||
|
auth_user_in_space(driver, driver_ctx),
|
||||||
|
update_space(driver, driver_ctx),
|
||||||
|
delete_space(driver, driver_ctx),
|
||||||
|
retrieve_spaces_users(driver, driver_ctx),
|
||||||
|
retrieve_spaces_posts(driver, driver_ctx),
|
||||||
|
create_new_post(driver, driver_ctx),
|
||||||
|
retrieve_all_posts(driver, driver_ctx),
|
||||||
|
retrieve_post(driver, driver_ctx),
|
||||||
|
update_post(driver, driver_ctx),
|
||||||
|
delete_post(driver, driver_ctx),
|
||||||
|
}
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
ServeApi(r, "api", rsrc_endpoints, methods, functions, "7477")
|
||||||
|
log.Println("API routes configured with prefix", "/api")
|
||||||
|
|
||||||
|
addr := "0.0.0.0:7476"
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
WriteTimeout: time.Second * 15,
|
||||||
|
ReadTimeout: time.Second * 15,
|
||||||
|
IdleTimeout: time.Second * 60,
|
||||||
|
Handler: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run our server in a goroutine so that it doesn't block.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("Router is running on port 7476")
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+Shift+C)
|
||||||
|
signal.Notify(c, os.Interrupt)
|
||||||
|
|
||||||
|
// Block until we receive our signal.
|
||||||
|
<-c
|
||||||
|
|
||||||
|
// Create a deadline to wait for.
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(30))
|
||||||
|
defer cancel()
|
||||||
|
// Doesn't block if no connections, but will otherwise wait
|
||||||
|
// until the timeout deadline.
|
||||||
|
srv.Shutdown(ctx)
|
||||||
|
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
||||||
|
// <-ctx.Done() if your application should wait for other services
|
||||||
|
// to finalize based on context cancellation.
|
||||||
|
log.Println("shutting down")
|
||||||
|
wg.Wait()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
serve()
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Serve an API using mux.Router().Host({domain}).Subrouter().
|
||||||
|
// Provide the router, domain, an array of endpoints and functions, and
|
||||||
|
// the port you would like the API accessible to.
|
||||||
|
func ServeApi(
|
||||||
|
router *mux.Router,
|
||||||
|
prefix string,
|
||||||
|
endpoints []string,
|
||||||
|
methods []string,
|
||||||
|
functions []func(http.ResponseWriter, *http.Request),
|
||||||
|
port string,
|
||||||
|
) {
|
||||||
|
|
||||||
|
apiR := router.PathPrefix(prefix).Subrouter()
|
||||||
|
|
||||||
|
for i := range endpoints {
|
||||||
|
apiR.HandleFunc(endpoints[i], functions[i]).Methods(methods[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
apiSrv := &http.Server{
|
||||||
|
Addr: "0.0.0.0:" + port,
|
||||||
|
WriteTimeout: time.Second * 15,
|
||||||
|
ReadTimeout: time.Second * 15,
|
||||||
|
IdleTimeout: time.Second * 60,
|
||||||
|
Handler: apiR,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := apiSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("API server is running on port " + port)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
// "encoding/json"
|
||||||
|
// "fmt"
|
||||||
|
//"io"
|
||||||
|
// "log"
|
||||||
|
"net/http"
|
||||||
|
// "os"
|
||||||
|
// "os/signal"
|
||||||
|
//"strings"
|
||||||
|
// "sync"
|
||||||
|
// "time"
|
||||||
|
// "github.com/gorilla/mux"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
)
|
||||||
|
|
||||||
|
func create_new_space(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_all_spaces(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_space(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func auth_user_in_space(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update_space(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete_space(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_spaces_posts(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_spaces_users(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
// "fmt"
|
||||||
|
"strconv"
|
||||||
|
//"io"
|
||||||
|
// "log"
|
||||||
|
"net/http"
|
||||||
|
// "os"
|
||||||
|
// "os/signal"
|
||||||
|
// "reflect"
|
||||||
|
"strings"
|
||||||
|
// "sync"
|
||||||
|
// "time"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
)
|
||||||
|
|
||||||
|
func create_new_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_all_users(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
user_id, _ := strconv.Atoi(mux.Vars(r)["user_id"])
|
||||||
|
|
||||||
|
result, _ := neo4j.ExecuteQuery(driver_ctx, driver,
|
||||||
|
"MATCH (u:User) WHERE id(u) = $user_id RETURN u.username AS username, u.email AS email, u.profile_picture AS profile_picture",
|
||||||
|
map[string]any{
|
||||||
|
"user_id": user_id,
|
||||||
|
}, neo4j.EagerResultTransformer,
|
||||||
|
neo4j.ExecuteQueryWithDatabase("neo4j"))
|
||||||
|
|
||||||
|
record := result.Records[0]
|
||||||
|
vals := record.AsMap()
|
||||||
|
user := struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
ProfilePicture string `json:"profile_picture"`
|
||||||
|
}{
|
||||||
|
Username: vals["username"].(string),
|
||||||
|
Email: vals["email"].(string),
|
||||||
|
ProfilePicture: vals["profile_picture"].(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
send_response(w, json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func auth_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
username := r.FormValue("username")
|
||||||
|
password_hash := r.FormValue("password_hash")
|
||||||
|
|
||||||
|
result, _ := neo4j.ExecuteQuery(driver_ctx, driver,
|
||||||
|
"MATCH (u:User) WHERE u.username = $username RETURN u.password_hash AS password_hash, elementId(u) AS id",
|
||||||
|
map[string]any{
|
||||||
|
"username": username,
|
||||||
|
}, neo4j.EagerResultTransformer,
|
||||||
|
neo4j.ExecuteQueryWithDatabase("neo4j"))
|
||||||
|
|
||||||
|
if len(result.Records) > 0 {
|
||||||
|
record := result.Records[0]
|
||||||
|
vals := record.AsMap()
|
||||||
|
if vals["password_hash"] != password_hash {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := strings.Split(vals["id"].(string), ":")[2]
|
||||||
|
|
||||||
|
ret := struct {
|
||||||
|
Auth bool `json:"auth"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
}{
|
||||||
|
Auth: true,
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(ret)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
send_response(w, json)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func update_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete_user(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_users_posts(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func retrieve_users_spaces(driver neo4j.DriverWithContext, driver_ctx context.Context) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-2
@@ -5,12 +5,18 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"erusev/parsedown": "^1.7",
|
"erusev/parsedown": "^1.7",
|
||||||
"vlucas/phpdotenv": "^5.6",
|
"vlucas/phpdotenv": "^5.6",
|
||||||
"wildbit/postmark-php": "^6.0"
|
"wildbit/postmark-php": "^6.0",
|
||||||
|
"laudis/neo4j-php-client": "^3.2"
|
||||||
},
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Josh Ashton",
|
"name": "Josh Ashton",
|
||||||
"email": "me@joshashton.dev"
|
"email": "me@joshashton.dev"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"php-http/discovery": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once 'db_functions.php';
|
require_once 'db.php';
|
||||||
require_once 'article_functions.php';
|
require_once 'article_functions.php';
|
||||||
|
|
||||||
use Postmark\PostmarkClient;
|
use Postmark\PostmarkClient;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once 'db_functions.php';
|
require_once 'db.php';
|
||||||
require_once 'functions.php';
|
require_once 'functions.php';
|
||||||
require_once 'account_functions.php';
|
require_once 'account_functions.php';
|
||||||
require_once 'article_functions.php';
|
require_once 'article_functions.php';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once 'db_functions.php';
|
require_once 'db.php';
|
||||||
|
|
||||||
function article_ids_by_newest()
|
function article_ids_by_newest()
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ header("Content-Security-Policy: default-src 'self'; font-src 'self' https://www
|
|||||||
|
|
||||||
// Initialize Composer.
|
// Initialize Composer.
|
||||||
require_once 'vendor/autoload.php';
|
require_once 'vendor/autoload.php';
|
||||||
require_once 'db_functions.php';
|
require_once 'db.php';
|
||||||
require_once 'account_functions.php';
|
require_once 'account_functions.php';
|
||||||
|
|
||||||
// Load environment variables.
|
// Load environment variables.
|
||||||
|
|||||||
Reference in New Issue
Block a user