Loading...
'; echo ''; } ?>
Leave feedback 1/4
Please leave feedback here to help us make this site better; you can choose to be anonymous.
We look over the feedback to know how we can improve the site and get what users want.
 
Do you want to be anonymous when leaving feedback?
Check for Yes
Next
Leave feedback 2/4
What is your overall impression of the layout?
Terrible AWESOME!!!
 
How easy is it to navigate the site?
Extremely easy How do I get out of here?
Next
Leave feedback 3/4
What is your overall impression of the functionality?
You call that functionality? Excellent!
 
How easy was it to understand what the site is about?
I still have no clue. I knew it before I came here!
Next
Leave feedback 4/4
Leave comments or suggestions here.
Send
Something went wrong; please check that you have marked all questions.
Start Forum Posts Leaderboard More















Mutation

Logged Out
Leaderboard coming soon

Engineering Solutions for the Church

Christian Engineering Solutions (CES) is a Not for Profit Organization specializing in collaborative solutions development for the Church. In the Spirit of Jesus Christ, we are to spread the gospel throughout the Earth, taking care to be good examples of Christ Jesus by serving others. CES is designed to help the Church meet these goals in the most rigorous manner possible.

Our Technology

Our Technology is scripture based in its goals and foundations. Open-Source and Free, one may use our services to both learn and solve problems with the goals of helping others and growing closer to God.

Features

Merit-based Reputation and Rewards

Streamlined Search and Citations

Modular Applications

Peer-to-Peer and Encryption Technologies (Under Development)

External Repository

https://github.com/ChristianEngineeringSolutions/ChristianEngineeringSolutions


Setup Account with email to earn rewards from reputation and donations.
Projects
Source Code

ADMIN
One Way , 8/8/2024
   Project in Projects
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
MIT License

Copyright (c) 2023 ChristianEngineeringSolutions

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Please cite: Christian Engineering Solutions
https://github.com/ChristianEngineeringSolutions/ChristianEngineeringSolutions

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
# Engineering Solutions for the Church

Christian Engineering Solutions (CES) is a Not for Profit Organization specializing in collaborative solutions development for the Church. In the Spirit of Jesus Christ, we are to spread the gospel throughout the Earth, taking care to be good examples of Christ Jesus by serving others. CES is designed to help the Church meet these goals in the most rigorous manner possible.

# Our Technology

Our Technology (Sasame) is scripture based in its goals and foundations. Open-Source and Free, one may use our services to both learn and solve problems with the goals of helping others and growing closer to God.

# Features

## Merit-based Reputation and Rewards
## Streamlined Search and Citations
## Modular Applications
## Peer-to-Peer and Encryption Technologies (Under Development)

# App

Currently:

Git clone

Update env file

install and run mongod

Use same email as on CES for full features

# Website

Public Version available at:
christianengineeringsolutions.com

Use the App for more features
(CES Connect, Filesystem, and More!)

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
#Version 2
#Blender add-on for christianengineeringsolutions.com
#Currently, copy/paste into blender scripts and run
# Official app on the way
# Combine models while maintaining sources
# Work on CES through blender!

import bpy
import requests
import json
import tempfile
import bpy.utils.previews
import os

directory   = os.path.join(bpy.utils.user_resource('SCRIPTS'), "presets", "scatter_presets_custom\\")
list_raw = []

sources = [];

#Where you want to save data
dir = "/home/uriah/Desktop/"

search = ''

website = "https://infinity-forum.org"
#website = "http://localhost:3000"

#for alerts
def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'):

    def draw(self, context):
        self.layout.label(text=message)

    bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)

page = 1
models = []
#Selected model (cursor)
selected = 0


power_list = {};

#    if model["title"] == "Test":
#        selected = model
#        req = requests.get('http://localhost:3000/uploads/' + selected["filename"])
#        file = req.content

#verify filename
#ShowMessageBox(obj["filename"]) 

#save model from server
#filepath = '/home/uriah/Desktop/new3.glb'
#tmp = tempfile.NamedTemporaryFile(delete=False)
#ShowMessageBox(tmp.name)
#tmp.write(file)

#with open(filepath, 'wb') as f:
#    f.write(file)

#import selected model into current scene
#imported_object = bpy.ops.import_scene.gltf(filepath=tmp.name)

#GOOD CODE

#imported_object = bpy.ops.import_scene.gltf(filepath=filepath)
#obj_object = bpy.context.selected_objects[0] ####<--Fix

# /GOOD CODE

#NOTES

#On Select get Passage as Chapter
#Next/Previous Iterate over Chapter

#/NOTES

def SearchModels(query="", which="Models", title=None, id=None):
    global models
    global dir
    models = []
    if which == "Models":
        data = {
            "query": query,
            "page": page
        }
        if id is None:
            r = requests.get(website + '/models', params=data)
        else:
            r = requests.get(website + '/models/' + title + '/' + id, params=data)
            print(r.text)
        if r.text is not None:
            models = json.loads(r.text)
        else:
            models = []
    #    ShowMessageBox(json.dumps(models))
        #List thumbnails
        for model in models:
        #    get thumbnail
            req = requests.get(website + '/uploads/' + model["thumbnail"])
            model["image"] = dir + model["thumbnail"]
            print(model["thumbnail"])
            print(model["image"])
            with open(dir + model["thumbnail"], 'wb') as f:
                f.write(req.content)
            power_list[model["title"]] = {
                "filepath": dir + model["filename"],
                "_id": model["_id"]
            }
    elif which == "SVGs":
        data = {
            "query": query,
            "page": page
        }
        r = requests.get(website + '/svgs', params=data)
        models = json.loads(r.text)
    #    ShowMessageBox(json.dumps(models))
        #List thumbnails
        for model in models:
        #    get thumbnail
            req = requests.get(website + '/uploads/' + model["thumbnail"])
            model["image"] = dir + model["thumbnail"]
            with open(dir + model["thumbnail"], 'wb') as f:
                f.write(req.content)
            power_list[model["title"]] = {
                "filepath": dir + model["filename"],
                "_id": model["_id"]
            }
    global custom_icons
    custom_icons = bpy.utils.previews.new()
    
    for model in models:
        if model["title"] == "SVG":
            ShowMessageBox(model["title"])
        if model["thumbnail"][:-4] not in custom_icons:
            custom_icons.load(model["thumbnail"][:-4], os.path.join(directory, model["image"]), 'IMAGE', force_reload=True)

SearchModels()

def SelectModel(_id, context):
    global models
    global dir
    global selected
    old = GetModel(selected)
    #deleted previously selected model and remove from sourcelist
    bpy.ops.object.delete()
    try:
        sources.remove(old)
    except:
        pass
    #put selected model under cursor and add to sourcelist
    AddObject(_id, context) #Also adds source
    index = 0
    for model in models:
        if model["_id"] == _id:
            return index
        index += 1
    selected = index
    
    pass

def CiteModel(_id, context):
    global models
    global dir
    #Add selected model under cursor and deselect
    #Add to sourcelist
    AddObject(_id, context)
    bpy.ops.object.select_all(action='DESELECT')

def AddObject(_id, context):
    global power_list
    global dir
    scene = context.scene
    mytool = scene.my_tool
    model = GetModel(_id)
        #    get file
    req = requests.get(website + '/uploads/' + model['filename'])
    with open(dir + model['filename'], 'wb') as f:
        f.write(req.content)
#        Import Object

    if mytool.which == "Models":
        imported_object = bpy.ops.import_scene.gltf(filepath=power_list[model['title']]["filepath"])
    elif mytool.which == "SVGs":
        imported_object = bpy.ops.wm.gpencil_import_svg(filepath=power_list[model['title']]["filepath"])
        
    obj_object = bpy.context.selected_objects[0] ####<--Fix
    
    
    new_source = power_list[model['title']]["_id"]
    added = False
#        Only add source if not there already
    for source in sources:
        if(new_source == source):
            added = True
            break
    if added == False:
        sources.append(power_list[model['title']]["_id"])

def GetModel(_id):
    global models
    for model in models:
        print(model["_id"])
        print(_id)
        if model["_id"] == _id:
            return model
    


bl_info = {
    "name": "CES Connect",
    "description": "Blender Add-On for Infinity-Forum.org",
    "author": "Uriah Sanders",
    "version": (0, 0, 1),
    "blender": (3, 0, 1),
    "location": "3D View > Tools",
    "warning": "", # used for warning icon and text in addons panel
    "wiki_url": "",
    "tracker_url": "",
    "category": "Development"
}


import bpy

from bpy.props import (StringProperty,
                       BoolProperty,
                       IntProperty,
                       FloatProperty,
                       FloatVectorProperty,
                       EnumProperty,
                       PointerProperty,
                       )
from bpy.types import (Panel,
                       Menu,
                       Operator,
                       PropertyGroup,
                       )


# ------------------------------------------------------------------------
#    Scene Properties
# ------------------------------------------------------------------------

class MyProperties(PropertyGroup):
    test_items = [
    ("Models", "Models", "", 1),
    ("SVGs", "SVGs", "", 2),
]
    
    search_str: StringProperty(
        name="",
        description=":",
        default="",
        maxlen=1024,
        )
        
    title_str: StringProperty(
        name="",
        description=":",
        default="Untitled",
        maxlen=1024,
        )
        
    username: StringProperty(
        name="",
        description=":",
        default="Username",
        maxlen=1024,
        )
    
    password: StringProperty(
        name="",
        description=":",
        default="Password",
        maxlen=1024,
        subtype="PASSWORD"
        )
        
    which: EnumProperty(
        name="",
        items=test_items,
        description="offers....",
    )

# ------------------------------------------------------------------------
#    Operators
# ------------------------------------------------------------------------

class Root(Operator):
    bl_label = "Root"
    bl_idname = "wm.root"

    def execute(self, context):
        scene = context.scene
        mytool = scene.my_tool
        global page
        
        page = 1
        SearchModels(mytool.search_str, mytool.which)

        return {'FINISHED'}

class Search(Operator):
    bl_label = "Search"
    bl_idname = "wm.search"

    def execute(self, context):
        scene = context.scene
        mytool = scene.my_tool
        global page
        
        page = 1
        SearchModels(mytool.search_str, mytool.which)

        return {'FINISHED'}

#Get previous thumbnail
class Previous(Operator):
    bl_label = "Previous"
    bl_idname = "wm.previous"

    def execute(self, context):
        scene = context.scene
        mytool = scene.my_tool
        global page
        global selected
        
        selected -= 1
        if selected < len(models):
            selected = 0
        SelectModel(models[selected]["_id"], context)

        return {'FINISHED'}

#Get next thumbnail
class Next(Operator):
    bl_label = "Next"
    bl_idname = "wm.next"

    def execute(self, context):
        scene = context.scene
        mytool = scene.my_tool
        global page
        global selected
        
        selected += 1
        if selected >= len(models):
            selected = len(models) - 1
        SelectModel(models[selected]["_id"], context)

        return {'FINISHED'}

class ViewMore(Operator):
    bl_label = "View More"
    bl_idname = "wm.view_more"

    def execute(self, context):
        scene = context.scene
        mytool = scene.my_tool
        global page
        
        page += 1
        
        SearchModels(mytool.search_str, mytool.which)
        

        return {'FINISHED'}

class Upload(Operator):
    bl_label = "Upload Scene"
    bl_idname = "wm.upload"

    def execute(self, context):
        scene = context.scene
        mytool = scene.my_tool
        
        which = mytool.which
#        Export and save file
        blend_file_path = bpy.data.filepath
        directory = os.path.dirname(blend_file_path)
        target_file = os.path.join(directory, 'myfile.glb')
        
        if which == "Models":
            target_file = os.path.join(directory, 'myfile.glb')
            bpy.ops.export_scene.gltf(filepath=target_file)
        elif which == "SVGs":
            target_file = os.path.join(directory, 'myfile.svg')
            bpy.ops.wm.gpencil_export_svg(filepath=target_file)
        
#        Upload to CES
        upload_data = {
            "sources": sources,
            "username": mytool.username,
            "password": mytool.password,
            "title": mytool.title_str
        }
        files = {'file': open(target_file,'rb')}
        if which == "Models":
            x = requests.post(website + "/upload_model", files=files, data=upload_data)
        elif which == "SVGs":
            x = requests.post(website + "/upload_svg", files=files, data=upload_data)
        


        return {'FINISHED'}

class Add_Object(Operator):
    bl_label = "Cite"
    bl_idname = "wm.cite"
    title: bpy.props.StringProperty()
    id: bpy.props.StringProperty()
    filename: bpy.props.StringProperty()

    def execute(self, context):
        global dir
        scene = context.scene
        mytool = scene.my_tool
            #    get file
        CiteModel(self.id, context)

        return {'FINISHED'}

#Add to scene
class Select(Operator):
    bl_label = "Select"
    bl_idname = "wm.select"
    #change title to selected when selected
    title: bpy.props.StringProperty()
    id: bpy.props.StringProperty()
    filename: bpy.props.StringProperty()

    def execute(self, context):
        global dir
        scene = context.scene
        mytool = scene.my_tool
            #    get file
        SelectModel(self.id, context)

        return {'FINISHED'}
    

#Display As Chapter
class Enter(Operator):
    bl_label = "Enter"
    bl_idname = "wm.enter"
    #change title to selected when selected
    title: bpy.props.StringProperty()
    id: bpy.props.StringProperty()
    filename: bpy.props.StringProperty()

    def execute(self, context):
        global dir
        scene = context.scene
        mytool = scene.my_tool
        # Get chapter
        SearchModels(mytool.search_str, mytool.which, self.title, self.id)

        return {'FINISHED'}

# ------------------------------------------------------------------------
#    Menus
# ------------------------------------------------------------------------

class OBJECT_MT_CustomMenu(bpy.types.Menu):
    bl_label = "Select"
    bl_idname = "OBJECT_MT_custom_menu"

    def draw(self, context):
        layout = self.layout

        # Built-in operators
        layout.operator("object.select_all", text="Select/Deselect All").action = 'TOGGLE'
        layout.operator("object.select_all", text="Inverse").action = 'INVERT'
        layout.operator("object.select_random", text="Random")

# ------------------------------------------------------------------------
#    Panel in Object Mode
# ------------------------------------------------------------------------

class OBJECT_PT_CustomPanel(Panel):
    bl_label = "CES Connect"
    bl_idname = "OBJECT_PT_custom_panel"
    bl_space_type = "VIEW_3D"   
    bl_region_type = "UI"
    bl_category = "Tools"
    bl_context = "objectmode"   


    @classmethod
    def poll(self,context):
        return context.object is not None

    def draw(self, context):
#        SearchModels()
        global custom_icons
        global models
        layout = self.layout
        scene = context.scene
        mytool = scene.my_tool
        
        layout.operator("wm.root")
        layout.separator()
        layout.label(text="Search")
        layout.prop(mytool, "which")
        layout.prop(mytool, "search_str")
        layout.operator("wm.search")
        layout.operator("wm.previous")
        layout.operator("wm.next")
        layout.separator()
        layout.prop(mytool, "title_str")
        layout.operator("wm.upload")
        layout.prop(mytool, "username")
        layout.prop(mytool, "password")
#        ShowMessageBox(json.dumps(models))
#        list out model titles
        for model in models:
            layout.label(text=model["title"])
            self.layout.template_icon(icon_value=custom_icons[model["thumbnail"][:-4]].icon_id,scale=10)
            operator = layout.operator("wm.cite")
            operator.title = model['title']
            operator.filename = model['filename']
            operator.id = model['_id']
            operator2 = layout.operator("wm.select")
            operator2.title = model['title']
            operator2.filename = model['filename']
            operator2.id = model['_id']
            operator3 = layout.operator("wm.enter")
            operator3.title = model['title']
            operator3.filename = model['filename']
            operator3.id = model['_id']
            layout.separator()
        
        layout.separator()
        layout.operator("wm.view_more")

# ------------------------------------------------------------------------
#    Registration
# ------------------------------------------------------------------------

classes = (
    MyProperties,
    Root,
    Search,
    Next,
    Previous,
    Upload,
    ViewMore,
    Add_Object,
    Select,
    Enter,
    OBJECT_MT_CustomMenu,
    OBJECT_PT_CustomPanel
)

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    bpy.types.Scene.my_tool = PointerProperty(type=MyProperties)

def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
    del bpy.types.Scene.my_tool
    for pcoll in custom_icons.values():
        print("Done.");
        bpy.utils.previews.remove(pcoll)
    custom_icons.clear()





if __name__ == "__main__":
    register()

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
import sys
import os
from PIL import Image

filename = sys.argv[1]
filepath = os.path.join(os.getcwd(), filename)
print(sys.argv[1])
image = Image.open(filepath)
if sys.argv[2] == 'png':
    image.save(filename, optimize=True, quality=80)
else:
    rgb_img = image.convert('RGB')
    rgb_img.save(filepath)
print("Finished compressing image.")

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
module.exports={
	apps: [{
		"script": "sasame.js",
  "watch": ["server", "client"],
  "ignore_watch" : ["node_modules", "dist/uploads", "dist/protected"]
	}]
}

0 Stars  
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
Last Stable Commit: 6ae15968cd27ed1f05a40914fd237cf8177c5ee5
TODO:
*=priority
Anonymous Posts
File compression (client side)
Minifications, optimizations
*Production Mode on Server (express)
SEO
Peer to Peer
*Admin interface //in progress
    Lock site for updates
    Approve daemons (done)
Encryption revisited
Tests
Show group passages on profile?
Database merge //mostly done
UUIDs (revisit; might be done)
Review Database Backups/Recovery
File Browser + Submissions
Tab sortable fix
Tab full testing and bugfixes
Non-profit filings
Replace CDNs*
Enable ads or alt sys
Editor live updates (updating language)

Add back recordings; voice activation;
real time UI updates for every action (long polling)

Need tabs?


Cordova*

Daemon search and pin
Daemon Mark
Multiple file upload?

blender add-on save temp files

recovery exp

---------------------------------------------------

Tasks:
Polish CES Connect (node app) (check all URLS)
Peer to Peer w/ Encryption (polish)
Tests
Insert AI passages into AI Chapter :)
Package app
Scripts in Blender extension
Chat Daemon w/ whisper
Room Creation
Keys in passage settings and metadata reader (review)
Search in Sidebar (search all/search bookmarks); bookmarks section
Blender crumple
Flex program (review)
Distraction free mode maintain border color (and thorough persistence checks/ improve algorithm)
Create Root passages from anywhere (button)
Anonymous support:
    Create Passage
    Bookmarks
    PPE
Minifications
Contact Us
All Basic Pages from modules
All Basic Features from modules
Bookmarks options in menu to Tools (or similar to open sidebar right)
Multiple windows (min. double page sort)
Sasamatic language
    Analyze passage on load, view more, and update

Keys as langs (or just sasamatic)
Lang auto change (Might be fine...revisit)
Upload directory option
LATEX inserts
Mathematical optimizations (for users)
Metadata in editor
Metadata per client
Session tabs
buttons from passages
Titles for ppe icons
Script for git push (tokens)
Server-side VMs
Astringent lock passages (revisit...maybe accomplished via alts)
Language auto update
New Design for Chapter passage
Deleted passages remain for sources?
Flagging/SafeSearch
Introduction Video
Otra idiomas auto support
Loading icon(s)
NOHTML
lang divs to concat multiple language in bubbled content //reconsider
Review Daemon Libs
Personal page plus button make default personal passage
Front Page (Splash page)
Editor Extensions
VS Code Plugin
Sasame Pure (All programming language support)
Browser Extensions
reference subs in sources for passage bubbling?
Begin Schematics
Canvas Key; adding canvas bitmaps to PPE
PPE Coloring options
Input/Output keys
FileStream Search
Git commands from filestream
backup commands from filestream
consider admin page > filestream
topbar tools (file edit view etc.)
Auto alternation (recursive with passage content)
Clean up Code/ Code Consistency / Conventions
Semsual $ distributions
View as PDF (quill 2 pdf) https://www.npmjs.com/package/quill-to-pdf
Modularize filestream

//Reconsider; may not be urgent; can already mix rich w/ code
Bubble Up Content All langs together (true mix; allow and revise) **Consider again (might already pretty much work)


Forum headers:
General: General Discussion
Suggestions: Suggestions for the website
Introductions: Introducing yourself to the community.
Science/Engineering: Posts about science and engineering.
Spirituality/Religion
Entertainment: Posts about TV/Games/Music/Books, etc.
Art: Art of all types!
Sports
Food
Education
Business
Government
Current Events

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
(async function(){
  console.log("Beginning video scanning.");
  const mongoose = require('mongoose');
  require('dotenv').config();
  // mongoose.connect('mongodb://127.0.0.1:27017/sasame', {
  //     useNewUrlParser: true,
  //     useCreateIndex: true,
  //     useFindAndModify: false,
  //     useUnifiedTopology: true
  // });
  const User = require('./models/User');
  const Passage = require('./models/Passage');
  const axios = require("axios");
  const https = require('https');
   const http = require('http');
  axios.create({
            httpAgent: new http.Agent({keepAlive: true}),
        });
  const tf = require("@tensorflow/tfjs-node");
  const nsfw = require("nsfwjs");
  var fs = require('fs'); 
  // var passage = await Passage.findOne({_id: process.argv[4].toString()});
  var passage = {flagged:false};
  // var request = require('request').defaults({ encoding: null });
  if(process.argv[5] == 'image'){
    const pic = await axios.get('http://localhost:3000/'+process.argv[2], {
      responseType: "arraybuffer",
    });
    console.log("Got pic.");
    const model = await nsfw.load(); // To load a local model, nsfw.load('file://./path/to/model/')
    // Image must be in tf.tensor3d format
    // you can convert image to tf.tensor3d with tf.node.decodeImage(Uint8Array,channels)
    const image = await tf.node.decodeImage(pic.data, 3);
    const predictions = await model.classify(image);
    image.dispose(); // Tensor memory must be managed explicitly (it is not sufficient to let a tf.Tensor go out of scope for its memory to be released).
    console.log(predictions);
    console.log("_ID: " + process.argv[4]);
    passage.isPorn = predictions[3].probability;
    passage.isHentai = predictions[4].probability;
    if(passage.isPorn > 0.6 || passage.isHentai > 0.6){
      passage.flagged = true;
    }
  }else if(process.argv[5] == 'video'){
    console.log("ISVIDEO");
    //process each screenshot
    for(var i = 1; i < 4; ++i){
      const pic = await axios.get('http://localhost:3000/'+ process.argv[3] + '/' + process.argv[6] + '_' + i + '.png', {
        responseType: "arraybuffer",
      });
      const model = await nsfw.load();
      const image = await tf.node.decodeImage(pic.data, 3);
      console.log("Processing Screenshot " + i);
      // Image must be in tf.tensor3d format
      // you can convert image to tf.tensor3d with tf.node.decodeImage(Uint8Array,channels)
      // const image = await tf.node.decodeImage(pic2.data, 3);
      const predictions = await model.classify(image);
      image.dispose(); // Tensor memory must be managed explicitly (it is not sufficient to let a tf.Tensor go out of scope for its memory to be released).
      console.log(predictions);
      // var passage = await Passage.findOne({_id: process.argv[4].toString()});
      passage.isPorn = predictions[3].probability;
      passage.isHentai = predictions[4].probability;
      if(passage.isPorn > 0.6 || passage.isHentai > 0.6){
        passage.flagged = true;
        break;
      }
    }
  }
    // delete flagged media
    if(passage.flagged){
        fs.unlink('dist/'+process.argv[2], function(err){
          if (err && err.code == 'ENOENT') {
              // file doens't exist
              console.info("File doesn't exist, won't remove it.");
          } else if (err) {
              // other errors, e.g. maybe we don't have enough permission
              console.error("Error occurred while trying to remove file");
          } else {
              console.info(`removed flagged media.`);
          }
      });
    }
    // await Passage.findOneAndUpdate({_id:process.argv[4]}, {flagged: passage.flagged});
    // await passage.save();
    console.log("Done.");
})();

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
//Working pseudo code

const passageController = require("./controllers/passageController");
const User = require("./models/User");

//getting messages

user.messages = [{
    foreignKey: 'Passage'
}];

//get messages for user
User.findOne({_id: user._id}).populate({path: 'messages', options: {sort: {'stars': '-1'}}});

//nevermind,
//just create model for Messages
//then star message on StarPassage for consistency

//clear out old messages for user
//...TODO

//notes for filestream

passage.fileStreamPath = '/somepath'; //unique key
//require admin to update path
//create if not exists in filesystem
//code is file content
//title is file name

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
echo "77opensesameCES" | sudo systemctl restart pm2-root
echo "77opensesameCES" | sudo systemctl restart nginx

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
'use strict';
const express = require('express');
const fileUpload = require('express-fileupload');
const querystring = require('querystring');
const bcrypt = require('bcrypt');
const mongoose = require('mongoose');
const bodyParser = require("body-parser");
const helmet = require('helmet');
const cors = require("cors");
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
require('dotenv').config();
const PORT = process.env.PORT || 3000;
var http = require('http');
const https = require('https');
var compression = require('compression');
const { promisify } = require('util');
const request = promisify(require('request'));
const browser = require('browser-detect');
var ffmpeg = require('fluent-ffmpeg');
const axios = require("axios"); //you can use any http client
const tf = require("@tensorflow/tfjs-node");
const nsfw = require("nsfwjs");
var fs = require('fs'); 
const fsp = require('fs').promises;
//for daemons access to help code
function DAEMONLIBS(passage, USERID){
    return `
    
    var THIS = `+JSON.stringify(passage)+`;
    var USERID = "`+(USERID)+`";
    // async function INTERACT(content){
    //     const result = await $.ajax({
    //         type: 'post',
    //         url: '`+process.env.DOMAIN+`/interact',
    //         data: {
    //             _id: _id
    //         }});
    //     return result;
    // }
    async function GETDAEMON(daemon, param){
        var passage = await GETPASSAGE(daemon);
        var code = passage.code;
        var parameter = await GETPASSAGE(param);
        //add Param Line
        code = "const PARAM = " + JSON.stringify(parameter) + ';' + code;
        
        //then eval code
        return code;
    }
    async function GETPASSAGE(_id){
        //run ajax
        const result = await $.ajax({
            type: 'get',
            url: '`+process.env.DOMAIN+`/get_passage',
            data: {
                _id: _id
            }});
        return result;
    }
    
    `;
}

// Models
const User = require('./models/User');
const Passage = require('./models/Passage');
const Interaction = require('./models/Interaction');
const Category = require('./models/Category');
const Subcat = require('./models/Subcat');
const Subforum = require('./models/Subforum');
const Visitor = require('./models/Visitor');
// Controllers
const passageController = require('./controllers/passageController');
// Routes
// const passageRoutes = require('./routes/passage');

var fs = require('fs'); 
var path = require('path');
const { exec } = require('child_process');
const { v4 } = require('uuid');

const FormData = require('form-data');

const writeFile = promisify(fs.writeFile);
const readdir = promisify(fs.readdir);
//pagination for home and profile
const DOCS_PER_PAGE = 10; // Documents per Page Limit (Pagination)

// Database Connection Setup
mongoose.connect(process.env.MONGODB_CONNECTION_URL, {
    useNewUrlParser: true,
    useCreateIndex: true,
    useFindAndModify: false,
    useUnifiedTopology: true
});

var app = express();
var server = http.Server(app);

//socket io
const io = require('socket.io')(server);
// const io = require("socket.io")(server, {
//     cors: {
//       origin: "https://example.com",
//       methods: ["GET", "POST"],
//       allowedHeaders: ["my-custom-header"],
//       credentials: true
//     }
  // });
app.use(express.urlencoded({ extended: false, limit: '1gb' }));
app.use(compression());
app.use(cors());
app.use(helmet());
app.use(fileUpload());

// make sure recordings folder exists
const recordingFolder = './dist/recordings/';
if (!fs.existsSync(recordingFolder)) {
  fs.mkdirSync(recordingFolder);
}


// Setup Frontend Templating Engine - ejs
const ejs = require('ejs');

// app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.json({
    verify: function (req, res, buf) {
      var url = req.originalUrl;
      if (url.startsWith('/stripe')) {
         req.rawBody = buf.toString();
      }
    }
  }));
  
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));

const MongoStore  = require('connect-mongo');

// User Session Setup Logic
const session = require('express-session')({
    secret: "ls",
    resave: true,
    saveUninitialized: true,
    store: MongoStore.create({ mongoUrl: process.env.MONGODB_CONNECTION_URL })
});
var sharedsession = require("express-socket.io-session");
const cookieParser = require('cookie-parser');
const nodemailer = require('nodemailer');
const scripts = {};
scripts.isPassageUser = function(user, passage){
    if(typeof user == 'undefined'){
        return false;
    }
    var ret;
    if(user._id.toString() == passage.author._id.toString()){
        return true;
    }
    var i = 0;
    for(const u of passage.users){
        if(u._id.toString() == user._id.toString()){
            return true;
        }
    }
    return false;
};
scripts.getCategoryTopicsNum = function (cat) {
    var num = 0;
    for(var c of cat.passages){
        if(c.forumType != 'subforum'){
            ++num;
        }
    }
    return num;
};
scripts.getNumPosts = function (cat){
    var num = 0;
    for(var c of cat.passages){
        if(c.forumType != 'subforum'){
            // ++num;
            num += c.passages.length;
        }
    }
    return num;
};
//for categories
scripts.lastPost = function(cat){
    if(cat.passages && cat.passages.length > 0){
    var passage = cat.passages.at(-1);
    return 'by ' + passage.author.name + '<br>' + passage.date.toLocaleDateString();
    }
    else{
        return 'No Posts Yet.'
    }
};
scripts.getNumViews = async function(id){
    var views = await Visitor.countDocuments({visited:id});
    // if(views == '' || !views){
    //     return 0;
    // }
    return views;
}
app.use(cookieParser());
app.use(session);
io.use(sharedsession(session, {
    autoSave: true
}));
// app.use('/protected/:pID', async function(req, res, next){
//     if(!req.session.user){
//         return res.redirect('/');
//     }
//     else{
//         var passage = await Passage.findOne({filename:req.params.pID});
//         if(passage != null)
//         if(passage.author._id.toString() != req.session.user._id.toString() && !scripts.isPassageUser(req.session.user, passage)){
//             return res.redirect('/');
//         }
//     }
//     next();
// });
app.use(express.static('./dist'));
app.set('view engine', 'ejs');
app.set('views', './views');
function getUploadFolder(passage){
    return passage.personal ? 'protected' : 'uploads';
}
app.use(async function(req, res, next) {
    //shortcuts for ejs
    res.locals.getUploadFolder = getUploadFolder;
    res.locals.user = req.session.user;
    res.locals.daemonLibs = DAEMONLIBS;
    res.locals.DOMAIN = process.env.DOMAIN;
    res.locals.LOCAL = process.env.LOCAL;
    if(!req.session.CESCONNECT){
        req.session.CESCONNECT = false;
    }
    res.locals.CESCONNECT = req.session.CESCONNECT;
    res.locals.fromOtro = req.query.fromOtro || false;
    //daemoncheck
    if(['stream', 'subforums', 'profile', '', 'passage', 'messages', 'leaderboard', 'donate', 'filestream', 'loginform', 'personal', 'admin', 'forum', 'projects', 'tasks', 'recover', 'recoverpassword'].includes(req.url.split('/')[1])){
        let daemons = [];
        if(req.session.user){
            let user = await User.findOne({_id: req.session.user._id}).populate('daemons');
            regenerateSession(req);
            daemons = user.daemons;
        }
        let defaults = await Passage.find({default_daemon: true}).populate('author users sourceList');
        if(defaults.length > 0)
            daemons = daemons.concat(defaults);
        for(const daemon of daemons){
            // daemon.code = DAEMONLIBS(daemon, req.session.user._id) + daemon.code;
            daemons[daemon] = bubbleUpAll(daemon);
        }
        res.locals.DAEMONS = daemons;
    }
    next();
});
// UNDER CONSTRUCTION PAGE
// app.all('*', async (req, res) => {
//     res.render('construction');
// });
//Serving Files
app.get('/jquery.min.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/jquery/dist/jquery.min.js');
});
app.get('/jquery-ui.min.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/jquery-ui-dist/jquery-ui.min.js');
});
app.get('/jquery-ui.css', function(req, res) {
    res.sendFile(__dirname + '/node_modules/jquery-ui-dist/jquery-ui.css');
});
app.get('/jquery.modal.min.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/jquery-modal/jquery.modal.min.js');
});
app.get('/jquery.modal.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/jquery-modal/jquery.modal.js');
});
app.get('/jquery.modal.min.css', function(req, res) {
    res.sendFile(__dirname + '/node_modules/jquery-modal/jquery.modal.min.css');
});
app.get('/data.json', function(req, res) {
    res.sendFile(__dirname + '/data.json');
});
app.get('/ionicons.esm.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/ionicons/dist/ionicons/ionicons.esm.js');
});
app.get('/ionicons.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/ionicons/dist/ionicons/ionicons.js');
});
app.get('/p-9c97a69a.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/ionicons/dist/ionicons/p-9c97a69a.js');
});
app.get('/p-c1aa32dd.entry.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/ionicons/dist/ionicons/p-c1aa32dd.entry.js');
});
app.get('/p-85f22907.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/ionicons/dist/ionicons/p-85f22907.js');
});
app.get('/quill.snow.css', function(req, res) {
    res.sendFile(__dirname + '/node_modules/quill/dist/quill.snow.css');
});
app.get('/quill.min.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/quill/dist/quill.min.js');
});
app.get('/highlight.css', function(req, res) {
    res.sendFile(__dirname + '/node_modules/highlight.js/styles/a11y-light.css');
});
app.get('/highlight.js', function(req, res) {
    res.sendFile(__dirname + '/node_modules/highlight.js/lib/index.js');
});
app.get('/quill-to-pdf.js', function(req, res) {
    res.send(__dirname + '/node_modules/quill-to-pdf/dist/src');
});

//CRON
var cron = require('node-cron');
const { exit } = require('process');
const { response } = require('express');
const e = require('express');
const Message = require('./models/Message');
const { copyPassage } = require('./controllers/passageController');
const Bookmark = require('./models/Bookmark');
// const { getMode } = require('ionicons/dist/types/stencil-public-runtime');
//run monthly cron
cron.schedule('0 12 1 * *', async () => {
    await rewardUsers();
    console.log('Monthly Cron ran at 12pm.');
});
function monthDiff(d1, d2) {
    var months;
    months = (d2.getFullYear() - d1.getFullYear()) * 12;
    months -= d1.getMonth();
    months += d2.getMonth();
    return months <= 0 ? 0 : months;
}
//Get total star count and pay out users
async function rewardUsers(){
    const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
    //80% of funds to users
    var usd = parseInt(await totalUSD());
    let users = await User.find({stripeOnboardingComplete: true});
    for(const user of users){
        //appropriate percentage based on stars
        //users get same allotment as they have percentage of stars
        let userUSD = parseInt((await percentStars(user.starsGiven)) * usd);
        const transfer = await stripe.transfers.create({
            amount: userUSD,
            currency: "usd",
            destination: user.stripeAccountId,
        });
    }
    console.log("Users paid");
}

//get percentage of total stars
async function percentStars(user_stars){
    let final = user_stars / (await totalStars());
    return final;
}
//get percentage of total usd
async function percentUSD(donationUSD){
    var amount = await totalUSD();
    if(amount == 0){
        return 1;
    }
    let final = donationUSD / (amount);
    return final;
}
async function totalUSD(){
    const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
    const balance = await stripe.balance.retrieve();
    var usd = 0;
    for(const i of balance.available){
        if(i.currency == 'usd'){
            usd = i.amount;
            break;
        }
    }
    return usd;
}
async function totalStars(){
    let users = await User.find({stripeOnboardingComplete: true});
    if(users == false){
        return 0;
    }
    var stars = 0;
    for(const user of users){
        stars += user.starsGiven;
    }
    return stars;
}
//function from string-similarity-js
//since i was having import issues
var stringSimilarity = function (str1, str2, substringLength, caseSensitive) {
    if (substringLength === void 0) { substringLength = 2; }
    if (caseSensitive === void 0) { caseSensitive = false; }
    if (!caseSensitive) {
        str1 = str1.toLowerCase();
        str2 = str2.toLowerCase();
    }
    if (str1.length < substringLength || str2.length < substringLength)
        return 0;
    var map = new Map();
    for (var i = 0; i < str1.length - (substringLength - 1); i++) {
        var substr1 = str1.substr(i, substringLength);
        map.set(substr1, map.has(substr1) ? map.get(substr1) + 1 : 1);
    }
    var match = 0;
    for (var j = 0; j < str2.length - (substringLength - 1); j++) {
        var substr2 = str2.substr(j, substringLength);
        var count = map.has(substr2) ? map.get(substr2) : 0;
        if (count > 0) {
            map.set(substr2, count - 1);
            match++;
        }
    }
    return (match * 2) / (str1.length + str2.length - ((substringLength - 1) * 2));
};
function passageSimilarity(passage, source){
    if(passage.lang == source.lang){
        if(passage.lang == 'rich'){
            return stringSimilarity(passage.content, source.content);
        }
        else if(passage.lang == 'mixed'){
            return stringSimilarity(passage.html + passage.css + passage.javascript, source.html + source.css + source.javascript);
        }
        else{
            return stringSimilarity(passage.code, source.code);
        }
    }
}
async function getLastSource(passage){
    var source = await Passage.findOne({_id:passage.sourceList.at(-1)});
    return source;
}
async function starPassage(req, amount, passageID, userID, deplete=true){
    let user = await User.findOne({_id: userID});
    //infinite stars on a local sasame
    if(user.stars < amount && process.env.REMOTE == 'true'){
        return "Not enough stars.";
    }
    if(deplete){
        user.stars -= amount;
    }
    let passage = await Passage.findOne({_id: passageID}).populate('author sourceList');
    var lastSource = await getLastSource(passage);
    var bonus = 0;
    //calculate bonus
    for(const source of passage.sourceList){
        if(passage.author._id.toString() != source.author._id.toString()){
            var similarity = passageSimilarity(passage, source);
            bonus += similarity > 0.1 ? similarity : 0;
        }
    }
    // if(lastSource != null){
    //     bonus = passageSimilarity(passage, lastSource);
    // }else{
    //     bonus = 0;
    // }
    // if(lastSource && lastSource.author._id.toString() == req.session.user._id.toString()){
    //     bonus = 0;
    // }
    //add stars to passage and sources
    passage.stars += amount + bonus;
    //star all sub passages (content is displayed in parent)
    if(passage.bubbling && passage.passages && !passage.public){
        for(const p of passage.passages){
            await starPassage(req, amount, p._id, userID, false);
        }
    }
    await starMessages(passage._id, amount);
    //you have to star someone elses passage to get stars
    if(passage.author._id.toString() != req.session.user._id.toString()){
        user.starsGiven += amount;
        if(passage.collaborators.length > 0){
            passage.author.stars += (amount + bonus)/passage.collaborators.length;
        }
        else{
            passage.author.stars += amount + bonus;
        }
        //give stars to collaborators if applicable
        //split stars with collaborators
        if(passage.collaborators.length > 0){
            for(const collaborator in passage.collaborators){
                if(collaborator == passage.author.email){
                    //we already starred the author
                    continue;
                }
                let collaber = await User.findOne({email:collaborator});
                if(collaber != null){
                    collaber.stars += (amount + bonus)/passage.collaborators.length;
                    await collaber.save();
                }
            }
        }
        await passage.author.save();
    }
    await user.save();
    await passage.save();
    //star each source
    var i = 0;
    for(const source of passage.sourceList){
        var bonus1 = i == 0 ? bonus : 0;
        await starMessages(source._id, amount);
        let sourceAuthor = await User.findOne({_id: source.author._id});
        //you won't get extra stars for citing your own work
        if(sourceAuthor._id.toString() != req.session.user._id.toString() && sourceAuthor._id.toString() != passage.author._id.toString()){
            source.stars += amount + bonus1;
            sourceAuthor.stars += amount + bonus1;
            await sourceAuthor.save();
            await source.save();
        }
        ++i;
    }
    return await fillUsedInListSingle(passage);
}
async function starMessages(passage, stars=1){
    //keep message stars aligned with passage
    var messages = await Message.find({passage: passage});
    for(const message of messages){
        message.stars += stars;
        await message.save();
    }
}
async function notifyUser(userId, content, type="General"){
    let notification = await Notification.create({
        user: userId,
        content: content,
        type: type
    });
}
//basically messages
async function sharePassage(from, _id, username){
    var user = await User.findOne({
        username: username
    });
    var passage = await Passage.findOne({
        _id: _id
    });
    if(user != null){
        var message = await Message.create({
            from: from,
            to: user._id,
            passage: _id,
            title: passage.title
        });
        return 'Message Sent';
    }
    return 'User not Found.';
}
app.post('/share_passage', async(req, res) => {
    res.send(await sharePassage(req.session.user._id, req.body.passageId, req.body.username));
});
app.get('/messages', async(req, res) => {
    //paginate messages
    //...TODO

    //serve messages
    //sort them by stars
    var messages = await Message.find({
        to: req.session.user._id
    }).populate('passage').sort('-stars').limit(DOCS_PER_PAGE);
    var passages = [];
    for(const message of messages){
        var p = await Passage.findOne({
            _id: message.passage._id
        }).populate('author users sourcelist');
        passages.push(p);
    }
    for(const passage of passages){
        passages[passage] = bubbleUpAll(passage);
    }
    let bookmarks = [];
        // if(req.session.user){
        //     bookmarks = await User.find({_id: req.session.user._id}).populate('bookmarks').passages;
        // }
        if(req.session.user){
            bookmarks = getBookmarks(req.session.user);
        }
    passages = await fillUsedInList(passages);
    res.render('messages', {
        passages: passages,
        subPassages: false,
        passageTitle: false, 
        scripts: scripts, 
        passage: {id:'root', author: {
            _id: 'root',
            username: 'Sasame'
        }},
        bookmarks: bookmarks,
    });
});
//get highest rank passage with title
async function bestOf(title){
    return await Passage.find({title:title, personal: false}).sort('-stars').limit(1)[0];
}
//get best passage in best module for passage
//hard to tell the difference from the above function
//still thinking about it...
//but this is better (more useful), I think...
async function bestOfPassage(title){
    var parent = await Passage.find({title:title, public: true, personal: false}).sort('-stars').limit(1)[0];
    if(parent != null){
        var sub = await Passage.find({parent: parent._id, personal: false}).sort('-stars').limit(1);
        return sub;
    }
    return 'No public passage "' + title + '"';
}
app.get('/bestOf/:title', async(req, res) => {
    res.send(await bestOfPassage(req.params.title));
});
//ROUTES
app.get('/personal/:user_id', async (req, res) => {
    if(!req.session.user || req.session.user._id != req.params.user_id){
        return res.redirect('/');
    }
    else{
        var passages = await Passage.find({
            //author: req.params.user_id, 
            personal: true,
            users: {
                $in: [req.params.user_id]
            }
        }).populate('users').limit(DOCS_PER_PAGE);
        for(const passage of passages){
            passages[passage] = bubbleUpAll(passage);
        }
        let bookmarks = [];
        // if(req.session.user){
        //     bookmarks = await User.find({_id: req.session.user._id}).populate('bookmarks').passages;
        // }
        if(req.session.user){
            bookmarks = getBookmarks(req.session.user);
        }
        passages = await fillUsedInList(passages);
        const ISMOBILE = browser(req.headers['user-agent']).mobile;
        return res.render("stream", {
            subPassages: false,
            passageTitle: 'Christian Engineering Solutions', 
            scripts: scripts, 
            passages: passages, 
            page: 'personal',
            passage: {id:'root', author: {
                _id: 'root',
                username: 'Sasame'
            }},
            bookmarks: bookmarks,
            ISMOBILE: ISMOBILE,
            whichPage: 'personal'
        });
    }
});

//GET (or show view)
app.get("/profile/:username?/:_id?/", async (req, res) => {
    let bookmarks = [];
    let profile;
    if(typeof req.params.username == 'undefined' || !req.params._id){
        if(!req.session.user){
            return res.redirect('/');
        }
        profile = req.session.user;
    }
    else{
        profile = await User.findOne({_id: req.params._id});
    }
    if(profile == null){
        return res.redirect('/');
    }
    let find = {
        //author: profile, 
        users: {
            $in: [profile]
        },
        deleted: false, 
        personal: false
    };
    //if it's their profile show personal passages
    // if(req.session.user && profile._id.toString() == req.session.user._id.toString()){
    //     find.$or = [{personal: true}, {personal: false}];
    // }
    let passages = await Passage.find(find).populate('author users sourceList').sort({stars: -1, _id: -1}).limit(DOCS_PER_PAGE);
    for(const passage of passages){
        passages[passage] = bubbleUpAll(passage);
    }
    // if(req.session.user){
    //     bookmarks = await User.find({_id: req.session.user._id}).populate('passages').passages;
    // }
    if(req.session.user){
        bookmarks = getBookmarks(req.session.user);
    }
    var usd = 0;
    usd = parseInt((await percentStars(profile.starsGiven)) * (await totalUSD()));
    if(isNaN(usd)){
		usd = 0;
	}
    passages = await fillUsedInList(passages);
    res.render("profile", {usd: (usd/100), subPassages: false, passages: passages, scripts: scripts, profile: profile,
    bookmarks: bookmarks,
    whichPage: 'profile',
    thread: false,
    passage: {id:'root', author: {
                _id: 'root',
                username: 'Sasame'
            }}
    });
});
app.get('/loginform', function(req, res){
    res.render('login_register', {scripts: scripts});
  });
app.post('/get_username_number', async function(req, res){
    let name = req.body.name;
    let number = await User.countDocuments({name:name.trim()}) + 1;
    res.send(number + '');
});
//HOME/INDEX
app.get('/rex_login', async (req, res) => {
    res.render('rex_login');
});
async function getFullPassage(_id){
    //get fully populated passage as JSON
    var passage = await Passage.findOne({_id: _id});
}
async function alternate(passageID, iteration, prevs){
    console.log(iteration);
    var passage = await Passage.findOne({_id: passageID});
    var find = {
        title: passage.title,
        _id: {
            $ne: passage._id
        }
    };
    var numDocuments = await Passage.countDocuments(find);
    if(iteration >= numDocuments){
        return false;
    }
    var test = await Passage.find(find);
    var alternate = await Passage.find(find).sort('-stars').populate('author users sourceList').skip(parseInt(iteration)).limit(1);
    alternate = alternate[0];

    return alternate;
}
app.get('/alternate', async(req, res) => {
    //UPDATE TODO
    //Okay, so, we need to get the _id of the parent passage
    //then splice/replace in the alternate passage
    //and then return the whole deal :)
    //\UPDATE TODO
    var parent = await Passage.findOne({_id: req.query.parentID});
    var passage = await alternate(req.query.passageID, req.query.iteration, req.query.altPrevs);
    if(!passage){
        return res.send("restart");
    }
    console.log('?' + passage.content);
    //return sub false rendered view of parent
    //...
    //I know and sorry this is a lot of duplicated code from passage view route
    //but TODO will see if we can put all this into a function
    var subPassages = await Passage.find({parent: parent._id, personal: false}).populate('author users sourceList');
    //reorder sub passages to match order of passage.passages
    var reordered = Array(subPassages.length).fill(0);
    for(var i = 0; i < parent.passages.length; ++i){
        for(var j = 0; j < subPassages.length; ++j){
            if(subPassages[j]._id.toString() == parent.passages[i]._id.toString()){
                reordered[i] = subPassages[j];
            }
        }
    }
    //idk why but sometimes in production there were extra 0s...
    //need to test more and bugfix algorithm above
    reordered = reordered.filter(x => x !== 0); //just get rid of extra 0s
    if(parent.passages.length < 1){
	    reordered = subPassages;
    }
    parent.passages = reordered;
    // if(parent._id.toString() == req.query.passageID){
    //     parent = passage[0];
    // }
    // else{
        // parent.passages.forEach(function(p, i){
        //     if(i == req.query.position){
        //         parent.passages[i] = passage;
        //     }
        // });
        var i = 0;
        for(const p of parent.passages){
            if(i == req.query.position){
                parent.passages[i] = passage;
            }
            ++i;
        }
    // }
    // if(typeof parent.passages != 'undefined' && parent.passages[0] != null){
    //     for(const p of parent.passages){
    //         parent.passages[p] = bubbleUpAll(p);
    //     }
    // }
    parent = bubbleUpAll(parent);
    if(parent.displayHTML.length > 0 || parent.displayCSS.length > 0 || parent.displayJavascript.length > 0){
        parent.showIframe = true;
    }
    if(passage){
        return res.render('passage', {
            subPassages: parent.passages,
            passage: parent,
            sub: false,
            altIteration: '_' + req.query.iteration
        });
    }
    else{
        return res.send('restart');
    }
});
app.post('/save_alternate', async(req, res) => {
    var original = await Passage.findOne({_id: req.body.passageID});
    var passage = await passageController.copyPassage(original, [req.session.user], null, async function(){

    });
    passage.passages = [];
    for(const p of JSON.parse(req.body.passages)){
        var full = await Passage.findOne({_id:p});
        var newPassage = await passageController.copyPassage(full, [req.session.user], null, async function(){

        });
        newPassage.parent = passage;
        await newPassage.save();
        passage.passages.push(newPassage);
    }
    await passage.save();
    await bookmarkPassage(passage, req.session.user._id);
    return res.send("Bookmarked Alternate Module.");
});
// function getFromRemote(url){
//     var request = https.request(remoteURL, function(response){
//         response.setEncoding('utf8');
//         response.on('data', function(data){
//             var final = data.replaceAll("/css/", "https://christianengineeringsolutions.com/css/");
//             var final = data.replaceAll("/js/", "https://christianengineeringsolutions.com/js/");
//             var final = data.replaceAll("/images/", "https://christianengineeringsolutions.com/images/");
//             return res.send(final);
//         });
//     });
//     request.end();
// }


/**
 * Save associated files,
 * Add novel sourceLink for pushed/pulled passages
 */
if(process.env.LOCAL == 'true'){
    //send a passsage from local sasame to remote
    app.post('/push', async (req, res) => {
        const fsp = require('fs').promises;
        var passage = await Passage.findOne({_id: req.body._id}).populate('author users sourceList');

          var url = 'https://christianengineeringsolutions.com/pull';
          //TODO add file
          var file = await fsp.readFile('./dist/uploads/' + passage.filename, "base64");
          var data = querystring.stringify({
            passage : JSON.stringify(passage),
            file: file
        });
          var options = {
              hostname: 'christianengineeringsolutions.com',
              path: '/pull',
              method: 'POST',
              thumbnail: '',
              headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Content-Length': Buffer.byteLength(data)
              }
          }
          
          var request = https.request(options, function (response) {
            // done
            console.log('statusCode:', res.statusCode);
            console.log('headers:', res.headers);
            response.setEncoding('utf8');
            var fin = '';
            response.on('data', (d) => {
                fin += d;
                console.log("body: " + d);
            });
            response.on('end', function(){
                res.send(fin);
            });
          });
          request.on('error', (e) => {
            console.error(e);
          });  
          console.log(data);        
          request.write(data);
          request.end();
    });
}
//recieve a passage from remote
app.post('/pull', async (req, res) => {
    const fsp = require('fs').promises;
    //all pulled passages start off at root level
    //copy passage
    var passage = JSON.parse(req.body.passage);
    var uploadTitle = v4();
    passage.sourceList = [];
    passage.sourceLink = process.env.DOMAIN + '/' + encodeURIComponent(passage.title) + '/' + passage._id;
    var pushingAuthor = await User.findOne({email: passage.author.email}) || req.session.user;
    //TODO: modify copy to ensure thumbnail creation onload
    //...
    //local is recieving a passage from a remote sasame
    //associate proper file
    if(process.env.LOCAL == 'true'){
        var copy = await passageController.copyPassage(passage, [pushingAuthor || req.session.user], null, function(){

        });
        //bookmark passage
        await bookmarkPassage(copy._id, pushingAuthor._id);
        if(passage.filename){
            uploadTitle = uploadTitle + passage.filename.split('.').at(-1);
        }
        copy.filename = uploadTitle;
        await copy.save();
        //file from passage 

        const file = await fs.createWriteStream('./dist/uploads/' + uploadTitle);
        const request = https.get('https://christianengineeringsolutions.com/uploads/' + passage.filename, function(response){
            response.pipe(file);
            file.on('finish', () => {
                file.close();
            });
        });
        return res.redirect('/');
    }
    //remote is recieving passage from a local sasame
    else if(process.env.REMOTE == 'true'){
        var user = await authenticateUsername(passage.author.email, req.body.password);
        if(user){
            var copy = await passageController.copyPassage(passage, [pushingAuthor || req.session.user], null, function(){

            });
            //bookmark passage
            await bookmarkPassage(copy._id, pushingAuthor._id);
            copy.sourceLink = null;
            copy.collaborators.push(copy.author.email);
            //file from form sent by requests module
            //(local sasame may not have public URL)
            //upload main file
            copy.filename = uploadTitle;
            await copy.save();
            var buf = Buffer.from(req.body.file, 'base64');
            await fsp.writeFile('./dist/uploads/'+uploadTitle, buf);
            return res.send('https://christianengineeringsolutions.com/passage/' + encodeURIComponent(copy.title) + '/' + copy._id);
        }
        else{
            return res.send("Wrong Credentials.");
        }
    }
});


//use email to assign transferred (push or pull) passage to the correct author for merit
function updateTransferredPassageAuthor(){

}

app.get('/get_passage', async (req, res) => {
    //run authentication for personal passages
    var passage = await Passage.findOne({_id: req.query._id})
    if(!passage.personal || (passage.personal && req.session.user._id.toString() == passage.author._id.toString())){
        return res.send(JSON.stringify(passage));
    }
    else{
        return res.send('Improper Credentials.');
    }
});
app.post('/passage_from_json', async (req, res) => {
    //copy passage
    var copy = passageController.copyPassage(req.params.passage, [req.session.user], null, function(){
        
    });
    await bookmarkPassage(copy._id, req.session.user._id);
});
function getRemotePage(req, res){
    //get same route from server
    var route = req.originalUrl + '?fromOtro=true';
    const remoteURL = 'https://christianengineeringsolutions.com' + route;
    var output = '';
    var request = https.request(remoteURL, function(response){
        response.setEncoding('utf8');
        response.on('data', function(data){
            var final = data.replaceAll("/css/", "https://christianengineeringsolutions.com/css/");
            final = final.replaceAll("/js/", "https://christianengineeringsolutions.com/js/");
            final = final.replaceAll("/eval/", "https://christianengineeringsolutions.com/eval/");
            final = final.replaceAll("/images/", "https://christianengineeringsolutions.com/images/");
            final = final.replaceAll("/jquery", "https://christianengineeringsolutions.com/jquery");
            final = final.replaceAll("https://unpkg.com/three@0.87.1/exampleshttps://christianengineeringsolutions.com/js/loaders/GLTFLoader.js", "https://unpkg.com/three@0.87.1/examples/js/loaders/GLTFLoader.js");
            final = final.replaceAll("/ionicons.esm.js", "https://christianengineeringsolutions.com/ionicons.esm.js");
            final = final.replaceAll("/ionicons.js", "https://christianengineeringsolutions.com/ionicons.js");
            output += final;
        });
        response.on('end', function(){
            var script = `
            <script>
                $(function(){
                    var html = '<ion-icon data-cesconnect="true"style="float:left;"class="green"id="remote_toggle"title="Remote"src="/images/ionicons/sync-circle.svg"></ion-icon>';
                    $(document).on('click', '#remote_toggle', function(){
                        //green
                        if($(this).css('color') == 'rgb(0, 128, 0)'){
                            $(this).css('color', 'red');
                            $.ajax({
                                type: 'post',
                                url: '/cesconnect/',
                                data: {},
                                success: function(data){
                                    window.location.reload();
                                }
                            });
                        }
                        else{
                            $(this).css('color', 'rgb(0, 128, 0)');
                            $.ajax({
                                type: 'post',
                                url: '/cesconnect/',
                                data: {},
                                success: function(data){
                                    window.location.reload();
                                }
                            });
                        }
                    });
                    $('#main_header').prepend(html);
                    $(document).on('click', '[id^="passage_pull_"]', function(e){
                        var _id = $(this).attr('id').split('_').at(-1);
                        //submit proper form
                        $('#pull_form_' + _id).submit();
                        flashIcon($('#passage_pull_' + _id), 'green');
                    });
                    $(document).on('click', '.rex_cite', function(){
                        var _id = ''; //get from DOM
                        //1. Get passage from remote
                        $.ajax({
                            type: 'get',
                            url: 'https://christianengineeringsolutions/get_passage',
                            data: {
                                _id: _id,
                            },
                            success: function(data){
                                flashIcon($('#transfer_bookmark_' + _id), 'green');
                                $('#passage_wrapper').append(data);
                                //2. update details to local
                                $.ajax({
                                    type: 'post',
                                    url: '/passage_from_json',
                                    data: {
                                        passage: data,
                                    },
                                    //this route should also bookmark the passage
                                    success: function(data){
                                       //show some success alert
                                       alert("Done"); //temp
    
                                    }
                                });


                            }
                        });

                    });
                });
            </script>
            `;
            return res.send(output + script);
        });
    });
    request.end();
}
app.post('/cesconnect', function(req, res){
    req.session.CESCONNECT = !req.session.CESCONNECT;
    res.send("Done.");
});
async function fillUsedInListSingle(passage){
        passage.usedIn = [];
        var ps = await Passage.find({
            sourceList: {
                $in: [passage._id]
            }
        });
        for(const p of ps){
            var record = await Passage.findOne({_id: p._id});
            passage.usedIn.push('<a href="/passage/'+record.title+'/'+record._id+'">'+record.title+'</a>');
        }
    return passage;
}
async function fillUsedInList(passages){
    if(passages.length > 0)
    for(const passage of passages){
        passage.usedIn = [];
        var ps = await Passage.find({
            sourceList: {
                $in: [passage._id]
            }
        });
        for(const p of ps){
            var record = await Passage.findOne({_id: p._id});
            passage.usedIn.push('<a href="/passage/'+record.title+'/'+record._id+'">'+record.title+'</a>');
        }
    }
    return passages;
}
async function getPassageLocation(passage, train){
    train = train || [];
    if(passage.parent == null){
        train.push('Infinity');
        return train.reverse();
    }
    else{
        passage.parent = await Passage.findOne({_id:passage.parent._id});
        train.push(passage.parent.title == '' ? 'Untitled' : passage.parent.title);
        return await getPassageLocation(passage.parent, train);
    }
}
async function returnPassageLocation(passage){
    var location = (await getPassageLocation(passage)).join('/');
    // return passage.parent ? passage.parent.title + passage.parent.parent.title : '';
    return '<a style="word-wrap:break-word;"href="'+(passage.parent ? ('/passage/' + passage.parent.title + '/' + passage.parent._id) : '/stream') +'">' + location + '</a>';
}
app.get('/stream', async (req, res) => {
    const ISMOBILE = browser(req.headers['user-agent']).mobile;
    //REX
    if(req.session.CESCONNECT){
        getRemotePage(req, res);
    }
    else{
        let fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
        let urlEnd = fullUrl.split('/')[fullUrl.split('/').length - 1];
        let passageTitle = fullUrl.split('/')[fullUrl.split('/').length - 2];
        let golden = '';
        let addPassageAllowed = true;
        let addChapterAllowed = true;
        var user = req.session.user || null;
        let passages = await Passage.find({
            deleted: false,
            personal: false,
        }).populate('author users sourceList parent').sort('-stars').limit(DOCS_PER_PAGE);
        for(const passage of passages){
            passages[passage] = bubbleUpAll(passage);
            passage.location = await returnPassageLocation(passage);
        }
        let passageUsers = [];
        let bookmarks = [];
        // if(req.session.user){
        //     bookmarks = await User.find({_id: req.session.user._id}).populate('bookmarks').passages;
        // }
        if(req.session.user){
            bookmarks = getBookmarks(req.session.user);
        }
        passages = await fillUsedInList(passages);
        res.render("stream", {
            subPassages: false,
            passageTitle: false, 
            scripts: scripts, 
            passages: passages, 
            passage: {id:'root', author: {
                _id: 'root',
                username: 'Sasame'
            }},
            bookmarks: bookmarks,
            ISMOBILE: ISMOBILE,
            page: 'stream',
            whichPage: 'stream'
        });
    }
});
//index.html
app.get('/', async (req, res) => {
    //REX
    if(req.session.CESCONNECT){
        getRemotePage(req, res);
    }
    else{
        const isMobile = browser(req.headers['user-agent']).mobile;
        let fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
        let urlEnd = fullUrl.split('/')[fullUrl.split('/').length - 1];
        let passageTitle = fullUrl.split('/')[fullUrl.split('/').length - 2];
        let golden = '';
        let addPassageAllowed = true;
        let addChapterAllowed = true;
        var user = req.session.user || null;
        let passages = await Passage.find({
            deleted: false,
            personal: false,
        }).populate('author users sourceList').sort({stars: -1, _id: -1}).limit(DOCS_PER_PAGE);
        for(const passage of passages){
            passages[passage] = bubbleUpAll(passage);
        }
        let passageUsers = [];
        let bookmarks = [];
        // if(req.session.user){
        //     bookmarks = await User.find({_id: req.session.user._id}).populate('bookmarks').passages;
        // }
        if(req.session.user){
            bookmarks = getBookmarks(req.session.user);
        }
        res.render("index", {
            subPassages: false,
            passageTitle: false, 
            scripts: scripts, 
            passages: passages, 
            passage: {id:'root', author: {
                _id: 'root',
                username: 'Sasame'
            }},
            bookmarks: bookmarks,
        });
    }
});
function sortArray(arr, to){
    var reordered = Array(arr.length).fill(0);
    for(var i = 0; i < to.length; ++i){
        for(var j = 0; j < arr.length; ++j){
            if(arr[j]._id.toString() == to[i]._id.toString()){
                reordered[i] = arr[j];
            }
        }
    }
    reordered = reordered.filter(x => x !== 0);
    return reordered;
}
app.get('/forum', async (req, res) => {
    // await clearForum();
    // await fillForum(req);
    let bookmarks = [];
    if(req.session.user){
        bookmarks = getBookmarks(req.session.user);
    }
    var infinity = await Passage.findOne({forumType: 'header'});
    var categories = await Passage.find({forumType: 'category'});
    var subcats = await Passage.find({forumType: 'subcat'}).populate('passages.author');
    var subforums = await Passage.find({forumType: 'subforum'});
    //get categories and subofrums in order as sorted
    var header = await Passage.findOne({forumType: 'header'});
    categories = sortArray(categories, infinity.passages);
    var sortedSubcats = [];
    //sort subcats
    //get list of all subcats in order from category passages
    for(const cat of categories){
        for(const c of cat.passages){
            sortedSubcats.push(c);
        }
    }
    subcats = sortArray(subcats, sortedSubcats);
    var sortedSubforums = [];
    //sort subforums
    //get list of all subforums in order from subcat passages
    for(const sub of subcats){
        for(const s of sub.subforums){
            sortedSubforums.push(s);
        }
    }
    subforums = sortArray(subforums, sortedSubforums);
    if(req.query._id){
        return res.render("forum_body", {
            scripts: scripts,
            bookmarks: bookmarks,
            categories: categories,
            subcats: subcats,
            subforums: subforums,
        });
    }
    res.render("forum", {
        scripts: scripts,
        bookmarks: bookmarks,
        categories: categories,
        subcats: subcats,
        subforums: subforums,
    });
    // await Subforum.deleteMany({});
    // fillForum();
});
async function getBigPassage(req, res, params=false, subforums=false){
    const ISMOBILE = browser(req.headers['user-agent']).mobile;
    let fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
    let urlEnd = fullUrl.split('/')[fullUrl.split('/').length - 1];
    let passageTitle = fullUrl.split('/')[fullUrl.split('/').length - 2];
    var passage_id = req.query._id;
    if(params){
        passage_id = req.params.passage_id;
    }
    var page = req.query.page || req.params.page || 1;
    console.log(page);
    var passage = await Passage.findOne({_id: passage_id.toString()}).populate('parent author users sourceList subforums');
    try{
        var mirror = await Passage.findOne({_id:passage.mirror._id});
        passage.sourceList.push(mirror);
    }
    catch(e){
        var mirror = null;
    }
    try{
        var bestOf = await Passage.findOne({parent:passage.bestOf._id}).sort('-stars');
        passage.sourceList.push(bestOf);
    }
    catch(e){
        var bestOf = null;
    }
    var replacement = mirror == null ? bestOf : mirror;
    var replacing = false;
    replacement = bestOf == null ? mirror : bestOf;
    if(replacement != null){
        replacing = true;
    }
    if(passage == null){
        return false;
    }
    var totalDocuments = await Passage.countDocuments({
        parent: passage._id
    })
    var totalPages = Math.floor(totalDocuments/DOCS_PER_PAGE) + 1;
    if(passage.personal == true && !scripts.isPassageUser(req.session.user, passage)){
        return res.send("Must be on Userlist");
    }
    passage.showIframe = false;
    // if(passage == null){
    //     return res.redirect('/');
    // }
    let passageUsers = [];
    if(passage.users != null && passage.users[0] != null){
        // passage.users.forEach(function(u){
        //     passageUsers.push(u._id.toString());
        // });
        for(const u of passage.users){
            passageUsers.push(u._id.toString());
        }
    }
    if(replacing){
        passage.passages = replacement.passages;
    }
    if(replacing && passage.mirrorEntire && passage.mirror != null){
        passage.lang = replacement.lang;
        passage.title = replacement.title;
        passage.content = replacement.content;
        passage.code = replacement.code;
        passage.html = replacement.html;
        passage.css = replacement.css;
        passage.javascript = replacement.javascript;
    }
    if(replacing && passage.bestOfEntire && passage.bestOf != null){
        passage.lang = replacement.lang;
        passage.title = replacement.title;
        passage.content = replacement.content;
        passage.code = replacement.code;
        passage.html = replacement.html;
        passage.css = replacement.css;
        passage.javascript = replacement.javascript;
    }
    passage = bubbleUpAll(passage);
    if(replacing){
    replacement = bubbleUpAll(replacement);
    }
    console.log(passage.code);
    console.log(passage.displayCode);
    if(passage.public == true && !passage.forum){
        // var subPassages = await Passage.find({parent: passage_id}).populate('author users sourceList').sort('-stars').limit(DOCS_PER_PAGE);
        var subPassages = await Passage.paginate({parent: passage_id}, {sort: '-stars', page: page, limit: DOCS_PER_PAGE, populate: 'author users sourceList'});
        if(replacing){
            var subPassages = await Passage.paginate({parent: replacement._id}, {sort: '-stars', page: page, limit: DOCS_PER_PAGE, populate: 'author users sourceList'});
        }
        subPassages = subPassages.docs;
    }
    else{
        if(passage.displayHTML.length > 0 || passage.displayCSS.length > 0 || passage.displayJavascript.length > 0){
            passage.showIframe = true;
        }
        if(passage.forum){
            var subPassages = await Passage.paginate({parent: passage_id}, {sort: '_id', page: page, limit: DOCS_PER_PAGE, populate: 'author users sourceList'});
            if(replacing){
                var subPassages = await Passage.paginate({parent: replacement._id}, {sort: '_id', page: page, limit: DOCS_PER_PAGE, populate: 'author users sourceList'});
            }
            subPassages = subPassages.docs;
        }
        else{ 
            var subPassages = await Passage.find({parent: passage_id}).populate('author users sourceList');  
            if(replacing){
                var subPassages = await Passage.find({parent: replacement._id}).populate('author users sourceList');
            }
        }
    }
    //we have to do this because of issues with populating passage.passages foreign keys
    if(!passage.public && !passage.forum){
        //reorder sub passages to match order of passage.passages
        var reordered = Array(subPassages.length).fill(0);
        for(var i = 0; i < passage.passages.length; ++i){
            for(var j = 0; j < subPassages.length; ++j){
                if(subPassages[j]._id.toString() == passage.passages[i]._id.toString()){
                    reordered[i] = subPassages[j];
                }
            }
        }
        //idk why but sometimes in production there were extra 0s...
        //need to test more and bugfix algorithm above
        reordered = reordered.filter(x => x !== 0); //just get rid of extra 0s
    }
    else{
        var reordered = subPassages;
    }
    if(passage.passages.length < 1){
        reordered = subPassages;
    }
    passage.passages = reordered;
    for(const p of passage.passages){
        passage.passages[p] = bubbleUpAll(p);
    }
    if(passage.parent != null){
        var parentID = passage.parent._id;
    }
    else{
        var parentID = 'root';
    }
    passage = await fillUsedInListSingle(passage);
    passage.passages = await fillUsedInList(passage.passages);
    if(subforums){
        passage.passages = passage.subforums;
    }
    return {
        subPassages: passage.passages,
        passage: passage,
        passageTitle: passage.title,
        passageUsers: passageUsers, Passage: Passage, scripts: scripts, sub: false, passage: passage, passages: false, totalPages: totalPages, docsPerPage: DOCS_PER_PAGE,
        ISMOBILE: ISMOBILE,
        parentID: parentID

    };
}
async function logVisit(req){
    let ipAddress = req.ip; // Default to req.ip

    // Check Cloudflare headers for real client IP address
    if (req.headers['cf-connecting-ip']) {
    ipAddress = req.headers['cf-connecting-ip'];
    } else if (req.headers['x-forwarded-for']) {
    // Use X-Forwarded-For header if available
    ipAddress = req.headers['x-forwarded-for'].split(',')[0];
    }


    // Check other custom headers if needed


    const existingVisitor = await Visitor.findOne({ ipAddress });


    if (!existingVisitor) {
    // Create a new visitor entry
    const newVisitor = new Visitor({ ipAddress: ipAddress, user: req.session.user || null, visited: passage._id });
    await newVisitor.save();
    }
}
app.get('/thread', async (req, res) => {
    // ALT
    var bigRes = await getBigPassage(req, res);
    await logVisit(req);
    res.render("thread", {subPassages: bigRes.passage.passages, passageTitle: bigRes.passage.title, passageUsers: bigRes.passageUsers, Passage: Passage, scripts: scripts, sub: false, passage: bigRes.passage, passages: false, totalPages: bigRes.totalPages, docsPerPage: DOCS_PER_PAGE,
        ISMOBILE: bigRes.ISMOBILE,
        thread: true,
        parentID: bigRes.parentID,
        topicID: bigRes.passage._id
    });
});
app.get('/cat', async (req, res) => {
    var pNumber = req.query.pNumber;
    var search = req.query.search || '';
    var find = {
        parent: req.query._id.toString(),
        $or: [
            {title: {$regex:search,$options:'i'}},
            {content: {$regex:search,$options:'i'}},
            {code: {$regex:search,$options:'i'}},
        ],
    };
    var parent = await Passage.findOne({_id: req.query._id});
    console.log('search:'+search);
    var topics = await Passage.paginate(find, {sort: '-date', page: pNumber, limit: 20, populate: 'passages.author'});
    var totalDocuments = await Passage.countDocuments(find);
    var totalPages = Math.floor(totalDocuments/20) + 1;
    for (const topic of topics.docs){
        topic.numViews = await scripts.getNumViews(topic._id);
        if(topic.passages && topic.passages.length > 0){
            topic.lastPost = 'by ' + topic.passages.at(-1).author.name + '<br>' + topic.passages.at(-1).date.toLocaleDateString();
        }else{
            topic.lastPost = 'No Posts Yet.';
        }
    }
    topics = topics.docs;
    return res.render('cat', {
        _id: parent._id,
        name: parent.title,
        topics: topics,
        postCount: topics.length,
        totalPages: totalPages
    });
    // var s = false;
    // var categories = await Passage.find({forumType: 'category'});
    // var subcats = await Passage.find({forumType: 'subcat'});
    // var subforums = await Passage.find({forumType: 'subforum'});
    // var focus;
    // if(req.query.s){
    //     s = true;
    //     var subForum = await Passage.findOne({_id: req.query.s});
    // }
    // if(s == false){
    //     var topics = await Passage.find({
    //         parent: req.query.f,
    //         sub: false
    //     });
    // }
    // else{
    //     var topics = await Passage.find({
    //         parent: req.query.s,
    //         sub: true
    //     });
    // }
    // console.log(req.query);
    // if(!req.query.f && !req.query.s){
    //     console.log('TEST2');
    //     return res.render("forum_body", {
    //         categories: categories,
    //         subcats: subcats,
    //         subforums: subforums
    //     });
    // }
    // var parent = await Passage.findOne({_id: req.query.f});
    // res.render('cat', {
    //     name: subForum ? subForum.title : false || parent.title || '',
    //     topics: topics,
    //     postCount: topics.length
    // })
});
async function clearForum(){
    await Passage.deleteMany({forumSpecial: true});
}
//fill forum with presets
async function fillForum(req){
    const fsp = require('fs').promises;
    var file = await fsp.readFile('./dist/json/forum.json');
    var json = JSON.parse(file);
    //create over directory passage
    var infinity = await Passage.create({
        author: req.session.user,
        users: [req.session.user],
        parent: null,
        title: "Infinity Forum",
        forumSpecial: true,
        tracker: 0,
        forumType: 'header'
    });
    infinity = await Passage.findOne({forumType: 'header'});
    for(const category of json.categories){
        var passage = await Passage.create({
            author: req.session.user,
            users: [req.session.user],
            parent: infinity._id.toString(),
            title: category.name,
            forumSpecial: true,
            tracker: category.tracker,
            forumType: 'category'
        });
        infinity.passages.push(passage);
        infinity.markModified('passages');
        await infinity.save();
        for(const cat of json.subcats){
            if(cat.parentTracker == category.tracker){
                var passage2 = await Passage.create({
                    author: req.session.user,
                    users: [req.session.user],
                    parent: passage._id,
                    forum: true,
                    title: cat.name,
                    forumSpecial: true,
                    content: cat.desc,
                    tracker:cat.tracker,
                    parentTracker: category.tracker,
                    forumType: 'subcat'
                });
                passage.passages.push(passage2);
                passage.markModified('passages');
                await passage.save();
                for(const sub of json.subforum){
                    if(sub.parentTracker == cat.tracker){
                        var passage3 = await Passage.create({
                            author: req.session.user,
                            users: [req.session.user],
                            parent: passage2._id,
                            forum: true,
                            title: sub.name,
                            forumSpecial: true,
                            parentTracker: cat.tracker,
                            tracker: sub.tracker,
                            forumType: 'subforum',
                            sub: true
                        });
                        passage2.subforums.push(passage3);
                        passage2.markModified('subforums');
                        await passage2.save();
                    }
                }
            }
        }
    }
    // for(const category of json.categories){
    //     await Category.create({
    //         name: category.name,
    //         tracker: category.tracker
    //     });
    // }
    // for(const cat of json.subcats){
    //     await Subcat.create({
    //         parentTracker: cat.parentTracker,
    //         name: cat.name,
    //         desc: cat.desc,
    //         tracker: cat.tracker
    //     });
    // }
    // for(const sub of json.subforum){
    //     await Subforum.create({
    //         parentTracker: sub.parentTracker,
    //         tracker: sub.tracker,
    //         name: sub.name,
    //         desc: sub.desc
    //     });
    // }
    console.log("DONE.");
}
// app.get('/projects', async (req, res) => {
    
//     res.render("projects");
// });
app.get('/projects', async (req, res) => {
    const ISMOBILE = browser(req.headers['user-agent']).mobile;
    //REX
    if(req.session.CESCONNECT){
        getRemotePage(req, res);
    }
    else{
        let fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
        let urlEnd = fullUrl.split('/')[fullUrl.split('/').length - 1];
        let passageTitle = fullUrl.split('/')[fullUrl.split('/').length - 2];
        let golden = '';
        let addPassageAllowed = true;
        let addChapterAllowed = true;
        var user = req.session.user || null;
        let passages = await Passage.find({
            deleted: false,
            personal: false,
            public: false,
            forum: false
        }).populate('author users sourceList parent').sort('-stars').limit(DOCS_PER_PAGE);
        for(const passage of passages){
            passages[passage] = bubbleUpAll(passage);
            passage.location = await returnPassageLocation(passage);
        }
        let passageUsers = [];
        let bookmarks = [];
        // if(req.session.user){
        //     bookmarks = await User.find({_id: req.session.user._id}).populate('bookmarks').passages;
        // }
        if(req.session.user){
            bookmarks = getBookmarks(req.session.user);
        }
        passages = await fillUsedInList(passages);
        res.render("stream", {
            subPassages: false,
            passageTitle: false, 
            scripts: scripts, 
            passages: passages, 
            passage: {id:'root', author: {
                _id: 'root',
                username: 'Sasame'
            }},
            bookmarks: bookmarks,
            ISMOBILE: ISMOBILE,
            page: 'projects',
            whichPage: 'projects',
            thread: false
        });
    }
});
app.get('/tasks', async (req, res) => {
    const ISMOBILE = browser(req.headers['user-agent']).mobile;
    //REX
    if(req.session.CESCONNECT){
        getRemotePage(req, res);
    }
    else{
        let fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
        let urlEnd = fullUrl.split('/')[fullUrl.split('/').length - 1];
        let passageTitle = fullUrl.split('/')[fullUrl.split('/').length - 2];
        let golden = '';
        let addPassageAllowed = true;
        let addChapterAllowed = true;
        var user = req.session.user || null;
        let passages = await Passage.find({
            deleted: false,
            personal: false,
            public: true,
            forum: false
        }).populate('author users sourceList parent').sort('-stars').limit(DOCS_PER_PAGE);
        for(const passage of passages){
            passages[passage] = bubbleUpAll(passage);
            passage.location = await returnPassageLocation(passage);
        }
        let passageUsers = [];
        let bookmarks = [];
        // if(req.session.user){
        //     bookmarks = await User.find({_id: req.session.user._id}).populate('bookmarks').passages;
        // }
        if(req.session.user){
            bookmarks = getBookmarks(req.session.user);
        }
        passages = await fillUsedInList(passages);
        res.render("stream", {
            subPassages: false,
            passageTitle: false, 
            scripts: scripts, 
            passages: passages, 
            passage: {id:'root', author: {
                _id: 'root',
                username: 'Sasame'
            }},
            bookmarks: bookmarks,
            ISMOBILE: ISMOBILE,
            page: 'more',
            whichPage: 'tasks',
            thread: false
        });
    }
});
app.post('/interact', async (req, res) => {
    var interaction = await Interaction.create({
        keeper: req.body.keeper,
        user: req.body.userID,
        passage: req.body.passageID,
        control: req.body.control || 0,
        content: req.body.content
    });
    var passage = await Passage.findOne({_id: req.body.passageID});
    passage.interactions.push(interaction._id);
    passage.markModified('interactions');
    await passage.save();
    res.send("Done.");
});
app.get('/interactions', async (req, res) => {
    if(req.session.user._id .toString() == req.query.keeper || req.session.user._id.toString() == req.query.userID){
        var interactions = await Interaction.find({
            control: req.query.control,
            passage: req.query.passage,
            keeper: req.query.keeper,
            user: req.query.userID
        });
        return res.send(JSON.stringify(interactions));
    }
    return res.send(false);
});
app.get('/donate', async function(req, res){
    if(req.session.CESCONNECT){
        return getRemotePage(req, res);
    }
    var usd = await totalUSD();
    var stars = await totalStars();
    res.render('donate', {
        passage: {id: 'root'}, usd: (usd/100), stars: stars,
        donateLink: process.env.STRIPE_DONATE_LINK,
        subscribeLink: process.env.STRIPE_SUBSCRIBE_LINK
    });
});
//Search
app.post('/search_leaderboard/', async (req, res) => {
    var search = req.body.search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    let results = await User.find({

        username: {
        $regex: search,
        $options: 'i',
    }}).sort('-starsGiven').limit(20);
    if(search == ''){
        var rank = true;
    }
    else{
        var rank = false;
    }
    res.render("leaders", {
        users: results,
        page: 1,
        rank: rank
    });
});
app.post('/search_profile/', async (req, res) => {
    var search = req.body.search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    var find = {
        author: req.body._id,
        deleted: false,
        personal: false,
        $or: [
            {title: {$regex:search,$options:'i'}},
            {content: {$regex:search,$options:'i'}},
            {code: {$regex:search,$options:'i'}},
        ],
    };
    let results = await Passage.find(find).populate('author users sourceList').sort({stars: -1, _id: -1}).limit(DOCS_PER_PAGE);
    for(const result of results){
        results[result] = bubbleUpAll(result);
    }
    results = await fillUsedInList(results);
    res.render("passages", {
        passages: results,
        subPassages: false,
        sub: true
    });
});
app.post('/search_messages/', async (req, res) => {
    var search = req.body.search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    var find = {
        to: req.session.user._id,
        $or: [
            {title: {$regex:search,$options:'i'}},
            {content: {$regex:search,$options:'i'}},
            {code: {$regex:search,$options:'i'}},
        ],
    };
    var messages = await Message.find(find).populate('passage').sort('-stars').limit(DOCS_PER_PAGE);
    var passages = [];
    for(const message of messages){
        var p = await Passage.findOne({
            _id: message.passage._id
        }).populate('author users sourcelist');
        passages.push(p);
    }
    for(const passage of passage){
        passages[passage] = bubbleUpAll(passage);
    }
    passages = await fillUsedInList(passages);
    res.render("passages", {
        passages: passages,
        subPassages: false,
        sub: true
    });
});
app.post('/ppe_search/', async (req, res) => {
    var search = req.body.search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    let parent = req.body.parent == 'root' ? null : req.body.parent;
    let results = await Passage.find({
        parent: parent,
        deleted: false,
        personal: false,
        mimeType: 'image',
        title: {
        $regex: search,
        $options: 'i',
    }}).populate('author users sourceList').sort('-stars').limit(DOCS_PER_PAGE);
    for(const result of results){
        results[result] = bubbleUpAll(result);
    }
    res.render("ppe_thumbnails", {
        thumbnails: results,
    });
});
// async function fixMissingInParent(){
//     let passages = await Passage.find();
//     for(const passage of passages){
//         if(passage.parent != null){
//             console.log('test:'+passage.parent);
//             var parent = await Passage.findOne({_id: passage.parent.toString()});
//             console.log(parent);
//             console.log(parent.passages.includes(passage));
//             // if(!parent.passages.includes(passage)){
//             //     parent.passages.push(passages);
//             // }
//             // else{
//             //     continue;
//             // }
//         }
//     }
// }
// (async function(){
//     await fixMissingInParent();
// })();
app.post('/search_passage/', async (req, res) => {
    var search = req.body.search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    var find = {
        deleted: false,
        personal: false,
        parent: req.body._id,
        $or: [
            {title: {$regex:search,$options:'i'}},
            {content: {$regex:search,$options:'i'}},
            {code: {$regex:search,$options:'i'}},
        ],
    };
    let results = await Passage.find(find).populate('author users sourceList').sort('-stars').limit(DOCS_PER_PAGE);
    if(results.length < 1 && req.session.user){
        var parent = await Passage.findOne({_id: req.body._id});
        let users = [req.session.user._id];
        if(parent.public && !parent.personal){
            //by default additions should have the same userlist
            if(parent.users.includes(req.session.user._id)){
                users = parent.users;
            }
            else{
                for(const u of parent.users){
                    users.push(u);
                }
            }
            //can only add to private or personal if on the userlist
            if(!scripts.isPassageUser(user, parent) && (!parent.public || parent.personal)){
                //do nothing
            }
            else if(parent.public_daemon == 2 || parent.default_daemon){
                //do nothing
            }
            else if(parent.public){
                let passage = await Passage.create({
                    author: req.session.user._id,
                    users: users,
                    parent: req.body._id,
                    title: req.body.search,
                    public: true
                });
                parent.passages.push(passage);
                await parent.save();
                results = [passage];
            }
        }
    }
    for(const result of results){
        results[result] = bubbleUpAll(result);
    }
    results = await fillUsedInList(results);
    res.render("passages", {
        passages: results,
        subPassages: false,
        sub: true
    });
});
function escapeBackSlash(str){
    var temp = str.replace('\\', '\\\\');
    console.log(temp);
    return str;
}
app.post('/search/', async (req, res) => {
    var search = req.body.search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    // let exact = await Passage.findOne({personal:false,deleted:false,title:search});
    // if(exact == null && req.session.user){
    //     let passage = await Passage.create({
    //         author: req.session.user._id,
    //         users: [req.session.user._id],
    //         parent: null,
    //         title: search,
    //         public: true
    //     });
    // }
    var find = {
        deleted: false,
        personal: req.body.personal,
        $or: [
            {title: {$regex:search,$options:'i'}},
            {content: {$regex:search,$options:'i'}},
            {code: {$regex:search,$options:'i'}},
        ],
    };
    if(req.body.personal){
        find.users = {
            $in: [req.session.user._id]
        }
    }
    switch(req.body.whichPage){
        case 'tasks':
            find.public = true;
            find.forum = false;
            break;
        case 'projects':
            find.public = false;
            find.forum = false;
    }
    let results = await Passage.find(find).populate('author users sourceList parent').sort({stars: -1, _id: -1}).limit(DOCS_PER_PAGE);
    for(const result of results){
        results[result] = bubbleUpAll(result);
        result.location = await returnPassageLocation(result);
    }
    results = await fillUsedInList(results);
    res.render("passages", {
        passages: results,
        subPassages: false,
        sub: true
    });
});
async function getBookmarks(user){
    return await Bookmark.find({user:user._id}).populate('passage');
}
async function bookmarkPassage(_id, _for){
    let user = await User.findOne({_id: _for});
    // user.bookmarks.push(_id);
    // await user.save();
    var passage = await Passage.findOne({_id: _id});
    let bookmark = await Bookmark.create({
        user: user,
        passage: passage
    });
    return "Done.";
}
app.post('/bookmark_passage', async (req, res) => {
    if(req.body.content == ''){
        await bookmarkPassage(req.body._id, req.session.user._id);
    }
    else{
        let passage = await Passage.findOne({_id: req.body._id});
        let copy = await passageController.copyPassage(passage, [req.session.user], null, function(){});
        copy[req.body.which] = req.body.content;
        await copy.save();
        await bookmarkPassage(copy._id, req.session.user._id);
    }
    res.send('Done.');
});
// Add security if reactivating check if passage user first
// app.post('/copy_passage/', async (req, res) => {
//     let copy = await passageController.copyPassage(req, res, function(){
        
//     });
//     let passage = await Passage.findOne({_id: req.body._id}).populate('author users sourceList');
//     res.render('passage', {subPassages: false, passage: copy, sub: true});
// });
//same as citing
app.post('/transfer_bookmark', async (req, res) => {
    let _id = req.body._id;
    let parent = req.body.parent;
    //first check if parent allow submissions (is Public)
    if(parent !== 'root'){
        let parentPassage = await Passage.findOne({_id: parent});
        if(parentPassage.public === false && parentPassage.author.toString() != req.session.user._id.toString() && !scripts.isPassageUser(req.session.user, parentPassage)){
            return res.send("<h2 style='text-align:center;color:red;'>Passage is private. Ask to be on the Userlist, or consider Bookmarking it, and copying it over to your own passage (press \"cite\").</h2>");
        }
    }
    //get passage to copy
    let user;
    let passage = await Passage.findOne({_id: req.body._id});
    //reset author list
    if (typeof req.session.user === 'undefined' || req.session.user === null) {
        user = null;
    }
    else{
        user = [req.session.user];
    }
    parent = req.body.parent == 'root' ? null : req.body.parent;
    if(req.body.focus == 'false'){
        let copy = await passageController.copyPassage(passage, user, parent, function(){
            
        });
        copy = bubbleUpAll(copy);
        copy = await fillUsedInListSingle(copy);
        if(req.body.which && req.body.which == 'cat'){
            return res.render('cat_row', {subPassages: false, topic: copy, sub: true});
        }
        else{
            return res.render('passage', {subPassages: false, passage: copy, sub: true});
        }
    }else{
        //add passage to sourcelist
        parent = await Passage.findOne({_id: req.body.parent});
        parent.sourceList.push(passage._id);
        console.log(parent.sourceList);
        //remove duplicates
        parent.sourceList = Object.values(parent.sourceList.reduce((acc,cur)=>Object.assign(acc,{[cur._id.toString()]:cur}),{}));
        console.log(parent.sourceList);
        parent.markModified('sourcelist');
        await parent.save();
        var title = passage.title == '' ? 'Untitled' : passage.title;
        return res.send('<div data-token="'+passage._id+'"data-title="'+title+'"class="new-source">"'+title+'" Added to Sourcelist.</div>');
    }
});
app.get('/get_bookmarks', async (req, res) => {
    // let bookmarks = [];
    // if(req.session.user){
    //     let user = await User.findOne({_id: req.session.user._id}).populate('bookmarks');
    //     bookmarks = user.bookmarks;
    // }
    // for(const bookmark of bookmarks){
    //     bookmarks[bookmark] = bubbleUpAll(bookmark);
    // }
    var bookmarks = await Bookmark.find({user: req.session.user}).sort('-_id').populate('passage');
    // for(const bookmark of bookmarks){
    //     bookmarks[bookmark].passage = bubbleUpAll(bookmark.passage);
    // }
    for(const bookmark of bookmarks){
        if(bookmark.passage != null){
            if(bookmark.passage.mirror != null){
                if(bookmark.passage.mirrorEntire){
                    var mirror = await Passage.findOne({_id:bookmark.passage.mirror._id});
                    bookmark.passage.title = mirror.title;
                }
            }
            if(bookmark.passage.bestOf != null){
                if(bookmark.passage.bestOfEntire){
                    var mirror = await Passage.findOne({parent:bookmark.passage.bestOf._id}).sort('-stars');
                    bookmark.passage.title = mirror.title;
                }
            }
        }
    }
    res.render('bookmarks', {bookmarks: bookmarks});
});
app.get('/get_daemons', async (req, res) => {
    let daemons = [];
    if(req.session.user){
        let user = await User.findOne({_id: req.session.user._id}).populate('daemons');
        daemons = user.daemons;
    }
    let defaults = await Passage.find({default_daemon: true}).populate('author users sourceList');
    daemons = daemons.concat(defaults);
    res.render('daemons', {daemons: daemons});
});
app.post('/add_daemon', async (req, res) => {
    if(req.session.user){
        let passage = req.body._id;
        let daemon = await Passage.findOne({_id: passage});
        let user = await User.findOne({_id: req.session.user._id});
        user.daemons.push(daemon);
        user.markModified('daemons');
        await user.save();
        return res.render('daemons', {daemons: user.daemons});
    }
    else{
        return res.send('false');
    }
});
app.post('/remove_daemon', async (req, res) => {
    if(req.session.user){
        let passage = req.body._id;
        let daemon = await Passage.findOne({_id: passage});
        let user = await User.findOne({_id: req.session.user._id});
        // user.daemons.forEach(function(d, i){
        //     if(d._id.toString() == daemon._id.toString()){
        //         user.daemons.splice(i, 1);
        //     }
        // });
        var i = 0;
        for(const d of user.daemons){
            if(d._id.toString() == daemon._id.toString()){
                user.daemons.splice(i, 1);
            }
            ++i;
        }
        user.markModified('daemons');
        await user.save();
        return res.render('daemons', {daemons: user.daemons});
    }
    else{
        return res.send('false');
    }
});
app.post('/sort_daemons', async (req, res) => {
    if(req.session.user){
        var user = await User.findOne({_id: req.session.user._id});
        var daemonOrder = [];
        if(typeof req.body.daemonOrder != 'undefined'){
            var daemonOrder = JSON.parse(req.body.daemonOrder);
            let trimmedDaemonOrder = daemonOrder.map(str => str.trim());
            user.daemons = trimmedDaemonOrder;
            user.markModified('daemons');
            await user.save();
        }
        //give back updated passage
        return res.send('Done');
    }
    else{
        return res.send('false');
    }
});
app.get('/leaderboard', async (req, res) => {
    const ISMOBILE = browser(req.headers['user-agent']).mobile;
    if(req.session.CESCONNECT){
        return getRemotePage(req, res);
    }
    var page = req.query.page || 1;
    // let users = await User.find().sort('-starsGiven');
    let users = await User.paginate({}, {sort: '-starsGiven', page: page, limit: 20});
    users = users.docs;
    var i = 1;
    for(const user of users){
        user.rank = i + ((page-1)*20);
        ++i;
    }
    if(page == 1){
        return res.render('leaderboard', {passage: {id: 'root'},users: users, scripts: scripts,
    ISMOBILE: ISMOBILE, page: page, rank: true});
    }
    else{
        return res.render('leaders', {users: users, page: page, rank: false});
    }
});
app.post('/add_user', async (req, res) => {
    let passageId = req.body.passageId;
    let username = req.body.username;
    let user = await User.findOne({username: username});
    let passage = await Passage.findOne({_id: passageId});
    if(user && req.session.user && req.session.user._id.toString() == passage.author._id.toString()){
        passage.users.push(user._id.toString());
        passage.markModified('users');
        await passage.save();
        res.send("User Added");
    }
    else{
        res.send("User not found.");
    }
});
app.post('/add_collaborator', async (req, res) => {
    var passage = await Passage.findOne({_id: req.body.passageID});
    if(req.session.user && req.session.user._id.toString() == passage.author._id.toString()){
        if(!passage.collaborators.includes(req.body.email)){
            passage.collaborators.push(req.body.email);
            passage.markModified('collaborators');
        }
        //if possible add user
        let collabUser = await User.findOne({email: req.body.email});
        if(collabUser != null && !isPassageUser(collabUser, passage)){
            passage.users.push(collabUser._id);
            passage.markModified('users');
        }
        await passage.save();
        return res.send("User Added");
    }
    else{
        return res.send("Wrong permissions.");
    }
});
app.post('/passage_setting', async (req, res) => {
    let _id = req.body._id;
    let setting = req.body.setting;
    let user = await User.findOne({_id: req.session.user._id});
    let passage = await Passage.findOne({_id: _id}).populate('author');
    switch(setting){
        case 'private':
            if(passage.author._id.toString() == user._id.toString()){
                passage.public = !passage.public;
            }
            break;
        case 'public':
            if(passage.author._id.toString() == user._id.toString()){
                passage.public = !passage.public;
            }
            break;
        case 'forum':
            if(passage.author._id.toString() == user._id.toString()){
                passage.forum = !passage.forum;
            }
            break;
        case 'personal':
            if(passage.author._id.toString() == user._id.toString()){
                passage.personal = !passage.personal;
            }
            break;
        case 'cross-origin-allowed':
            if(passage.author._id.toString() == user._id.toString()){
                passage.personal_cross_origin = !passage.personal_cross_origin;
            }
            break;
        case 'request-public-daemon':
            if(passage.author._id.toString() == user._id.toString()){
                passage.public_daemon = 1;
            }
            break;
        case 'admin-make-public-daemon':
            if(user.admin){
                passage.public_daemon == 2 ? 1 :  2;
            }
            break;
        case 'admin-make-default-daemon':
            if(user.admin){
                passage.default_daemon = !passage.default_daemon;
            }
            break;
        case 'distraction-free':
            if(passage.author._id.toString() == user._id.toString()){
                passage.distraction_free = !passage.distraction_free;
            }
            break;
        case 'bubbling':
            if(passage.author._id.toString() == user._id.toString()){
                passage.bubbling = !passage.bubbling;
            }
            break;
    }
    await passage.save();
    res.send("Done")
});
app.post('/update_mirroring', async (req, res) => {
    let passage = await Passage.findOne({_id: req.body._id});
    if(req.session.user && req.session.user._id.toString() == passage.author._id.toString()){
        try{
            var mirror = await Passage.findOne({_id:req.body.mirror.trim()});
        }
        catch(e){
            console.log("Null value");
            var mirror = null;
        }
        try{
            var bestOf = await Passage.findOne({_id:req.body.bestOf.trim()});
        }
        catch(e){
            console.log("Null value");
            var bestOf = null;
        }
        if(mirror != null){
            passage.mirror = mirror._id;
        }
        else{
            passage.mirror = null;
        }
        if(bestOf != null){
            passage.bestOf = bestOf._id;
        }
        else{
            passage.bestOf = null;
        }
        passage.mirrorContent = req.body.mirrorContent;
        passage.mirrorEntire = req.body.mirrorEntire;
        passage.bestOfContent = req.body.bestOfContent;
        passage.bestOfEntire = req.body.bestOfEntire;
        await passage.save();
        return res.send("Done.");
    }
    else{
        return res.send("Not your passage.");
    }
});
app.post('/remove_user', async (req, res) => {
    let passageID = req.body.passageID;
    let userID = req.body.userID;
    let passage = await Passage.findOne({_id: passageID});
    if(req.session.user && req.session.user._id.toString() == passage.author._id.toString()){
        // passage.users.forEach(async function(u, index){
        //     if(u == userID){
        //         //remove user
        //         passage.users.splice(index, 1);
        //     }
        // });
        var index = 0;
        for(const u of passage.users){
            if(u == userID){
                //remove user
                passage.users.splice(index, 1);
            }
            ++index;
        }
        passage.markModified('users');
        await passage.save();
        res.send("Done.");
    }
});
async function regenerateSession(req){
    if(req.session.user){
        let user = await User.findOne({_id: req.session.user._id});
        req.session.user = user;
    }
}
app.post('/remove_bookmark', async (req, res) => {
    let _id = req.body._id;
    // let user = await User.findOne({_id: req.session.user._id});
    // user.bookmarks.forEach((bookmark, i) => {
    //     if(bookmark._id.toString() == _id.toString()){
    //         user.bookmarks.splice(i, 1);
    //     }
    // });
    await Bookmark.deleteOne({_id:_id});
    // await user.save();
    res.send("Done.");
});
app.post('/stripe_webhook', bodyParser.raw({type: 'application/json'}), async (request, response) => {
    // response.header("Access-Control-Allow-Origin", "*");
    // response.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

    const endpointSecret = process.env.STRIPE_ENDPOINT_SECRET_KEY;
    const payload = request.body;
  
    console.log("Got payload: " + payload);
    const sig = request.headers['stripe-signature'];
    let event;
    try {
        event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);
        console.log(event.type);
    } catch (err) {
        console.log(err);
        //console.log(response.status(400).send(`Webhook Error: ${err.message}`));
        return;
        // return response.status(400).send(`Webhook Error: ${err.message}`);
    }
    //if subscription created or ended
    //update subscription data in db
    //...
    // Handle the checkout.session.completed event
    // For Custom Amount Investment
    if (event.type === 'checkout.session.completed') {
        let amount = payload.data.object.amount_total;
        //Save recording passage in database and give user correct number of stars
        //get user from email
        var user = await User.findOne({email: payload.data.object.customer_details.email});
        if(user){
            user.stars += (await percentUSD(parseInt(amount))) * (await totalStars());
            await user.save();
        }
    }
    //For Subscriptions
    else if(event.type == "invoice.paid"){
        console.log(JSON.stringify(payload.data.object.subscription));
        var email = payload.data.object.customer_email;
        if(email != null){
            //they get stars
            //plus time bonus
            var subscriber = await User.findOne({email: email});
            subscriber.subscriptionID = payload.data.object.subscription;
            subscriber.subscribed = true;
            subscriber.lastSubscribed = new Date();
            let monthsSubscribed = monthDiff(subscriber.lastSubscribed, new Date());
            subscriber.stars += (await percentUSD(80 * monthsSubscribed)) * (await totalStars());
            await subscriber.save();
        }
    }
    else if(event.type == "invoice.payment_failed"){
        var email = payload.data.object.customer_email;
        if(email != null){
            var subscriber = await User.findOne({email: email});
            subscriber.subscribed = false;  
            await subscriber.save();
        }
    }
    else{
        console.log(event.type);
    }  
    response.status(200).end();
  });
  function getStarsFromUSD(usd){
    return percentUSD(usd) * totalStars();
  }
app.post('/unsubscribe', async function(req, res){
    if(req.session.user){
        console.log("here");
        var user = await User.findOne({_id: req.session.user._id});
        const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
        const deleted = await stripe.subscriptions.del(
            user.subscriptionID
        );
        user.subscribed = false;
        user.subscriptionID = null;
        await user.save();
        req.session.user = user;
    }
    res.send("Done.");
});
app.get('/eval/:passage_id', async function(req, res){
    if(req.session.CESCONNECT){
        return getRemotePage(req, res);
    }
    var passage_id = req.params.passage_id;
    var passage = await Passage.findOne({_id: passage_id});
    passage.all = '';
    // console.log(passage);
    //stick together code for all sub passages
    var all = {
        html: passage.html,
        css: passage.css,
        javascript: passage.javascript
    };
    if(passage.lang == 'daemon'){
        all.javascript = passage.code;
    }
    var userID = null;
    if(req.session.user){
        userID = req.session.user._id.toString();
    }
    all.javascript = DAEMONLIBS(passage, userID) + all.javascript;
    if(!passage.public){
        passage.code = DAEMONLIBS(passage, userID) + passage.code;
        passage = bubbleUpAll(passage);
    }
    res.render("eval", {passage: passage, all: all});
});
function concatObjectProps(passage, sub){
    sub = bubbleUpAll(sub);
    // console.log(sub.code);
    if(typeof passage.content != 'undefined')
        passage.displayContent += (typeof sub.displayContent == 'undefined' || sub.displayContent == '' ? '' : sub.displayContent);
    if(typeof passage.code != 'undefined')
        passage.displayCode += (typeof sub.displayCode == 'undefined' || sub.displayCode == '' ? '' : '\n' + sub.displayCode);
    if(typeof passage.html != 'undefined')
        passage.displayHTML += (typeof sub.displayHTML == 'undefined' || sub.displayHTML == '' ? '' : '\n' + sub.displayHTML);
    if(typeof passage.css != 'undefined')
        passage.displayCSS += (typeof sub.displayCSS == 'undefined' || sub.displayCSS == '' ? '' : '\n' + sub.displayCSS);
    if(typeof passage.javascript != 'undefined')
        passage.displayJavascript += (typeof sub.displayJavascript == 'undefined' || sub.displayJavascript == '' ? '' : '\n' + sub.displayJavascript);
    if(passage.mimeType[0] == 'video'){
        var filename = sub.filename[0];
        // console.log((filename + '').split('.'));
        //`+passage.filename.split('.').at(-1)+`
        passage.video += `
        <video class="passage-file-`+sub._id+`"style="display:none"id="passage_video_`+sub._id+`"class="passage_video"width="320" height="240" controls>
            <source src="/`+getUploadFolder(sub)+`/`+filename+`" type="video/`+sub.filename[0].split('.').at(-1)+`">
            Your browser does not support the video tag.
        </video>
        <script>
            $('#passage_video_`+sub._id+`').on('ended', function(){
                $(this).css('display', 'none');
                $(this).next().next().css('display', 'block');
                $(this).next().next().get(0).play();
            });
        </script>
        `;
    }
    else if(passage.mimeType[0] == 'audio'){
        var filename = sub.filename[0];
        // console.log((filename + '').split('.'));
        //`+passage.filename.split('.').at(-1)+`
        passage.audio += `
        <audio style="display:none"id="passage_audio_`+sub._id+`"class="passage_audio"width="320" height="240" controls>
            <source src="/`+getUploadFolder(sub)+`/`+filename+`" type="audio/`+sub.filename[0].split('.').at(-1)+`">
            Your browser does not support the audio tag.
        </audio>
        <script>
            $('#passage_audio_`+sub._id+`').on('ended', function(){
                $(this).css('display', 'none');
                $(this).next().next().css('display', 'block');
                $(this).next().next().get(0).play();
            });
        </script>
        `;
    }
    passage.sourceList = [...passage.sourceList, ...sub.sourceList];
}
function getAllSubData(passage){
    if(!passage.public && passage.passages && passage.bubbling){
        for(const p of passage.passages){
            if(typeof p == 'undefined'){
                return p;
            }
            p.displayContent = p.content;
            p.displayCode = p.code;
            p.displayHTML = p.html;
            p.displayCSS = p.css;
            p.displayJavascript = p.javascript;
            if(p.lang == passage.lang){
                concatObjectProps(passage, getAllSubData(p));
            }
        }
        // passage.passages.forEach((p)=>{
        //     if(typeof p == 'undefined'){
        //         return p;
        //     }
        //     p.displayContent = p.content;
        //     p.displayCode = p.code;
        //     p.displayHTML = p.html;
        //     p.displayCSS = p.css;
        //     p.displayJavascript = p.javascript;
        //     if(p.lang == passage.lang){
        //         concatObjectProps(passage, getAllSubData(p));
        //     }
        // });
    }
    return passage;
}
function bubbleUpAll(passage){
    if(typeof passage == 'undefined'){
        return passage;
    }
    if(passage.mimeType[0] == 'video'){
        passage.video = `
        <video id="passage_video_`+passage._id+`"class="passage_video"width="320" height="240" controls>
            <source src="/`+getUploadFolder(passage)+`/`+passage.filename[0]+`" type="video/`+passage.filename[0].split('.').at(-1)+`">
            Your browser does not support the video tag.
        </video>
        <script>
            $('#passage_video_`+passage._id+`').on('ended', function(){
                $(this).css('display', 'none');
                $(this).next().next().css('display', 'block');
                $(this).next().next().get(0).play();
            });
        </script>
        `;
    }
    else if(passage.mimeType[0] == 'audio'){
        passage.audio = `
        <audio id="passage_audio_`+passage._id+`"class="passage_audio"width="320" height="240" controls>
            <source src="/`+getUploadFolder(passage)+`/`+passage.filename[0]+`" type="audio/`+passage.filename[0].split('.').at(-1)+`">
            Your browser does not support the audio tag.
        </audio>
        <script>
            $('#passage_audio_`+passage._id+`').on('ended', function(){
                $(this).css('display', 'none');
                $(this).next().next().css('display', 'block');
                $(this).next().next().get(0).play();
            });
        </script>
        `;
    }
    passage.displayContent = passage.content;
    passage.displayCode = passage.code;
    passage.displayHTML = passage.html;
    passage.displayCSS = passage.css;
    passage.displayJavascript = passage.javascript;
    if(!passage.bubbling){
        return passage;
    }
    if(!passage.public && !passage.forum){
        return getAllSubData(passage);
    }
    return passage;
}
app.get('/passage/:passage_title/:passage_id/:page?', async function(req, res){
    if(req.session.CESCONNECT){
        return getRemotePage(req, res);
    }
    var bigRes = await getBigPassage(req, res, true);
    if(!bigRes){
        return res.redirect('/');
    }
    // console.log('TEST'+bigRes.passage.title);
    // bigRes.passage = await fillUsedInListSingle(bigRes.passage);
    // console.log('TEST'+bigRes.passage.usedIn);
    bigRes.subPassages = await fillUsedInList(bigRes.subPassages);
    var location = await getPassageLocation(bigRes.passage);
    res.render("stream", {subPassages: bigRes.subPassages, passageTitle: bigRes.passage.title, passageUsers: bigRes.passageUsers, Passage: Passage, scripts: scripts, sub: false, passage: bigRes.passage, passages: false, totalPages: bigRes.totalPages, docsPerPage: DOCS_PER_PAGE,
        ISMOBILE: bigRes.ISMOBILE,
        thread: false,
        page: 'more',
        whichPage: 'sub',
        location: location
    });
});
app.get('/subforums/:passage_title/:passage_id/:page?', async function(req, res){
    if(req.session.CESCONNECT){
        return getRemotePage(req, res);
    }
    var bigRes = await getBigPassage(req, res, true, true);
    if(!bigRes){
        return res.redirect('/');
    }
    // console.log('TEST'+bigRes.passage.title);
    // bigRes.passage = await fillUsedInListSingle(bigRes.passage);
    // console.log('TEST'+bigRes.passage.usedIn);
    bigRes.subPassages = await fillUsedInList(bigRes.subPassages);
    var location = await getPassageLocation(bigRes.passage);
    res.render("stream", {subPassages: bigRes.subPassages, passageTitle: bigRes.passage.title, passageUsers: bigRes.passageUsers, Passage: Passage, scripts: scripts, sub: false, passage: bigRes.passage, passages: false, totalPages: bigRes.totalPages, docsPerPage: DOCS_PER_PAGE,
        ISMOBILE: bigRes.ISMOBILE,
        thread: false,
        page: 'more',
        whichPage: 'subforums',
        location: location,
        subforums: true
    });
});
app.get('/get_big_passage', async function(req, res){
    console.log(req.query._id);
    var bigRes = await getBigPassage(req, res);
    if(!bigRes){
        return res.redirect('/');
    }
    // console.log('TEST'+bigRes.passage.title);
    // bigRes.passage = await fillUsedInListSingle(bigRes.passage);
    // console.log('TEST'+bigRes.passage.usedIn);
    bigRes.subPassages = await fillUsedInList(bigRes.subPassages);
    var location = await getPassageLocation(bigRes.passage);
    res.render("passage", {subPassages: bigRes.subPassages, passageTitle: bigRes.passage.title, passageUsers: bigRes.passageUsers, Passage: Passage, scripts: scripts, sub: false, passage: bigRes.passage, passages: false, totalPages: bigRes.totalPages, docsPerPage: DOCS_PER_PAGE,
        ISMOBILE: bigRes.ISMOBILE,
        thread: false,
        sub: true,
        page: 'more',
        whichPage: 'sub',
        location: location
    });
});
app.get('/stripeAuthorize', async function(req, res){
    if(req.session.user){
        // Generate a random string as `state` to protect from CSRF and include it in the session
        req.session.state = Math.random()
        .toString(36)
        .slice(2);
        var user = req.session.user;
        try {
            let accountId = user.stripeAccountId;
            const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
            // Create a Stripe account for this user if one does not exist already
            if (accountId === null) {
                console.log("No Account yet.");
                const account = await stripe.accounts.create({
                    type: 'express',
                    capabilities: {
                        transfers: {requested: true},
                    },
                  });
                try{
                    await User.updateOne({_id: user._id}, {stripeAccountId: account.id});
                }
                catch(error){
                    console.error(error);
                }
                // Create an account link for the user's Stripe account
                const accountLink = await stripe.accountLinks.create({
                    account: account.id,
                    refresh_url: 'https://infinity-forum.org/stripeAuthorize',
                    return_url: 'https://infinity-forum.org/stripeOnboarded',
                    type: 'account_onboarding'
                });
                // console.log(accountLink);
                // Redirect to Stripe to start the Express onboarding flow
                res.redirect(accountLink.url);
            }
            else{
                console.log("Already has account.");
                let account = await User.findOne({_id: user._id});
                console.log(account);
                const loginLink = await stripe.accounts.createLoginLink(account.stripeAccountId);
                res.redirect(loginLink.url);
            }
          } catch (err) {
            console.log('Failed to create a Stripe account.');
            console.log(err);
            // next(err);
          }
    }
});
app.get('/recover', async(req, res) => {
    res.render('recover');
});
app.post('/recover', async(req, res) => {
    let user = await User.findOne({email: req.body.email});
    if(user != null){
        user.recoveryToken = v4();
        await user.save();
        sendEmail(req.body.email, 'Recover Password: christianengineeringsolutions.com', 
        'https://christianengineeringsolutions.com/recoverpassword/'+user._id+'/'+user.recoveryToken);
        return res.render('recover_password', {token: null});
    }
    else{
        return res.send("No Such User Found.");
    }
});
app.get('/recoverpassword/:user_id/:token', async(req, res) => {
    let user = await User.findOne({_id: req.params.user_id});
    if(user.recoveryToken == req.params.token){
        res.render("recover_password", {token: req.params.token, _id: user._id});
    }
    else{
        res.redirect('/');
    }
});
app.post('/recover_password', async(req, res) => {
    if(req.body.newPassword == req.body.confirm){
        var user = await User.findOne({_id: req.body._id});
        bcrypt.hash(req.body.confirm, 10, async function (err, hash){
            if (err) {
              console.log(err);
            }
            user.password = hash;
            await user.save();
            req.session.user = user;
            res.redirect('/profile/'+ user.username + '/' + user._id);
          });
    }
});
//populate an array of objectIDs
async function populateArray(arr, which){

}
app.get('/stripeOnboarded', async (req, res, next) => {
    const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
    try {
        let user = await User.findOne({_id: req.session.user._id});
      // Retrieve the user's Stripe account and check if they have finished onboarding
      const account = await stripe.account.retrieve(user.stripeAccountId);
      if (account.details_submitted) {
        user.stripeOnboardingComplete = true;
        await user.save();
        res.redirect('/profile');
      } else {
        console.log('The onboarding process was not completed.');
        res.redirect('/profile');
      }
    } catch (err) {
      console.log('Failed to retrieve Stripe account information.');
      console.log(err);
      next(err);
    }
  });

app.post('/login', async function(req, res) {
    //check if email has been verified
    var user = await authenticateUsername(req.body.username, req.body.password);
    if(user){
        req.session.user = user;
        return res.redirect('/profile/'+ user.username + '/' + user._id);
    }
    else{
        return res.redirect('/loginform');
    }
});
app.get('/dbbackup', async (req, res) => {
    if(!req.session.user || !req.session.user.admin){
        return res.redirect('/');
    }
    var directory1 = __dirname + '/dump';
    var directory2 = __dirname + '/dist/images';
    var AdmZip = require("adm-zip");
    const fsp = require('fs').promises;
    var zip1 = new AdmZip();
    var zip2 = new AdmZip();
    //compress /dump and /dist/uploads then send
    const files = await readdir(directory1);
    for(const file of files){
        console.log(file);
        zip1.addLocalFolder(__dirname + '/dump/' + file);
    }
    return res.send(zip1.toBuffer());
});
app.get('/uploadsbackup', async (req, res) => {
    if(!req.session.user || !req.session.user.admin){
        return res.redirect('/');
    }
    var directory1 = __dirname + '/dist/uploads';
    var AdmZip = require("adm-zip");
    const fsp = require('fs').promises;
    var zip1 = new AdmZip();
    //compress /dump and /dist/uploads then send
    const files = await readdir(directory1);
    for(const file of files){
        console.log(file);
        zip1.addLocalFile(__dirname + '/dist/uploads/' + file);
    }
    return res.send(zip1.toBuffer());
});
//test
app.get('/admin', async function(req, res){
    const ISMOBILE = browser(req.headers['user-agent']).mobile;
    if(!req.session.user || !req.session.user.admin){
        return res.redirect('/');
    }
    else{
        //view all passages requesting to be a public daemon
        var passages = await Passage.find({public_daemon:1}).sort('-stars');
        let bookmarks = [];
        // if(req.session.user){
        //     bookmarks = await User.find({_id: req.session.user._id}).populate('bookmarks').passages;
        // }
        if(req.session.user){
            bookmarks = getBookmarks(req.session.user);
        }
        passages = await fillUsedInList(passages);
        return res.render("admin", {
            subPassages: false,
            ISMOBILE: ISMOBILE,
            test: 'test',
            passageTitle: 'Infinity Forum', 
            scripts: scripts, 
            passages: passages, 
            passage: {id:'root', author: {
                _id: 'root',
                username: 'Sasame'
            }},
            bookmarks: bookmarks,

        });
    }
});
async function userExists(email){
    var member = await User.findOne({email: email});
    if(member == null){
        return false;
    }
    return true;
}
app.post('/register/', async function(req, res) {
    if ((req.body.email ||
      req.body.username) &&
      req.body.password &&
      req.body.passwordConf && 
      req.body.password == req.body.passwordConf
      && req.body["g-recaptcha-response"]
      && !(await userExists(req.body.email))) {  

        const name = req.body.name;
        const response_key = req.body["g-recaptcha-response"];
        const secret_key = "6Ldgf0gpAAAAALUayL5did3npJvmacmngo1bNeTU";
        const options = {
        url: `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${response_key}`,
        headers: { "Content-Type": "application/x-www-form-urlencoded", 'json': true }
        }
        try {
        const re = await request(options);
        if (!JSON.parse(re.body)['success']) {
            return res.send({ response: "Failed" });
        }
        else{
            console.log("SUCCESS");
        }
        // return res.send({ response: "Successful" });
        } catch (error) {
            return res.send({ response: "Failed" });
        }

        let numUsers = await User.countDocuments({username: req.body.username.trim()}) + 1;
        
        var userData = {
        name: req.body.username || req.body.email,
        username: req.body.username.split(' ').join('.') + '.' + numUsers || '',
        password: req.body.password,
        token: v4()
        }  //use schema.create to insert data into the db
      if(req.body.email != ''){
        userData.email = req.body.email;
      }
      User.create(userData, async function (err, user) {
        if (err) {
          console.log(err);
        } else {
          req.session.user = user;
          //hash password
          bcrypt.hash(user.password, 10, function (err, hash){
            if (err) {
              console.log(err);
            }
            user.password = hash;
            user.save();
          });
          //send verification email
          if(user.email && user.email.length > 1){
            sendEmail(user.email, 'Verify Email for Christian Engineering Solutions', 
                `
                    https://infinity-forum.org/verify/`+user._id+`/`+user.token+`
                `);
          }
          res.redirect('/profile/');
        }
      });
    }
    else{
        res.redirect('/loginform');
    }
});
app.post('/update_settings/', async function(req, res) {
    if ((req.body.email ||
      req.body.name) &&
      req.body.password &&
      req.body.passwordConf && 
      req.body.password == req.body.passwordConf &&
      req.body.oldPassword) {  
        var user = await authenticateUsername(req.body.oldUsername, req.body.oldPassword);
        if(user){
            req.session.user = user;
            user.name = req.body.name;
            user.username = req.body.newUsername;
            user.email = req.body.email;
            user.password = await bcrypt.hash(req.body.password, 10);
            await user.save();
            req.session.user = user;
            return res.redirect('/profile/');
        }
    }
    else{
        return res.redirect("/profile");
    }
});
app.get('/logout', function(req, res) {
    if (req.session) {
        // delete session object
        req.session.destroy(function(err) {
          if(err) {
            return next(err);
          } else {
          }
        });
      }
    res.redirect('/');
});
app.post('/paginate', async function(req, res){
    let page = req.body.page;
    let profile = req.body.profile; //home, profile, or leaderboard (new: fileStream and Passages)
    let search = req.body.search;
    let parent = req.body.passage;
    console.log(profile);
    if(profile == 'false'){
        let find = {
            personal: false,
            $or: [
                {title: new RegExp(''+search+'', "i")},
                {content: new RegExp(''+search+'', "i")},
                {code: new RegExp(''+search+'', "i")},
            ]
        };
        switch(req.body.whichPage){
            case 'tasks':
                find.public = true;
                find.forum = false;
                break;
            case 'projects':
                find.public = false;
                find.forum = false;
                break;
            case 'personal':
                find.personal = true;
                find.users = {
                $in: [req.session.user._id]
                }
                break;
        }
        if(parent != 'root'){
            find.parent = parent;
        }
        if(profile != 'false'){
            find.author = profile;
        }

        if(req.body.from_ppe_queue){
            find.mimeType = 'image';
        }
        let passages = await Passage.paginate(find, {sort: {stars: -1, _id: -1}, page: page, limit: DOCS_PER_PAGE, populate: 'author users parent sourceList'});
        passages.docs = await fillUsedInList(passages.docs);
        for(const p of passages.docs){
            passages.docs[p] = bubbleUpAll(p);
            passages.docs[p].location = await returnPassageLocation(p);
        }
        if(!req.body.from_ppe_queue){
            // let test = await Passage.find({author: profile});
            // console.log(test);
            return res.render('passages', {
                subPassages: false,
                passages: passages.docs,
                sub: true
            });
        }
        else{
            res.render('ppe_thumbnails', {
                thumbnails: passages.docs,
            });
        }
    }
    else if(profile == 'messages'){
        console.log('messages');
        let find = {
            title: new RegExp(''+search+'', "i"),
            to: req.session.user._id
        };
        var messages = await Message.paginate(find,
        {sort: '-stars', page: page, limit: DOCS_PER_PAGE, populate: 'author users passage'});
        var passages = [];
        for(const message of messages.docs){
            var p = await Passage.findOne({
                _id: message.passage._id
            }).populate('author users sourcelist');
            passages.push(p);
        }
        for(const p of passages){
            passages[p] = bubbleUpAll(p);
        }
        res.render('passages', {
            passages: passages,
            subPassages: false,
            sub: true,
        });
    }
    else if(profile == 'filestream'){

    }
    else if(profile == 'leaderboard'){
        console.log("leaderboard!");
        let find = {
            username: new RegExp(''+search+'', "i")
        };
        if(search == ''){
            var rank = true;
        }
        else{
            var rank = false;
        }
        let users = await User.paginate(find, {sort: "-starsGiven", page: page, limit: DOCS_PER_PAGE});
        res.render('leaders', {users: users.docs, page: page, rank: rank});
    }
});

app.post(/\/delete_passage\/?/, (req, res) => {
    var backURL=req.header('Referer') || '/';
    passageController.deletePassage(req, res, function(){
        console.log('DELETED');
        res.send('Deleted');
    });
});

// app.use('/passage', passageRoutes);
app.get('/passage_form/', (req, res) => {
    res.render('passage_form');
});
async function createPassage(user, parentPassageId, subforums=false){
    let users = null;
    let parentId = null;
    var isRoot = parentPassageId == 'root';
    var parent;
    var personal = false;
    var fileStreamPath = null;
    if(user){
        users = [user];
    }
    if(isRoot){
        parentId = null;
    }
    else{
        parentId = parentPassageId;
        parent = await Passage.findOne({_id: parentId});
        if(parent.fileStreamPath != null && parent.fileStreamPath.slice(-1) == '/'){
            fileStreamPath = parent.fileStreamPath + 'Untitled';
        }
        personal = parent.personal;
        //by default additions should have the same userlist
        if(user){
            if(parent.users.includes(user._id)){
                users = parent.users;
            }
            else{
                for(const u of parent.users){
                    users.push(u);
                }
            }
        }
        //can only add to private or personal if on the userlist or is a forum
        if(!parent.forum && !scripts.isPassageUser(user, parent) && (!parent.public || parent.personal)){
            return res.send("Must be on userlist.");
        }
        else if(parent.public_daemon == 2 || parent.default_daemon){
            return res.send("Not allowed.");
        }
    }
    var lang = 'rich';
    if(parent && parent.lang){
        lang = parent.lang;
    }
    var forum = false;
    if(parent && parent.forum){
        forum = parent.forum;
    }
    let passage = await Passage.create({
        author: user,
        users: users,
        parent: parentId,
        forum: forum,
        lang: lang,
        fileStreamPath: fileStreamPath,
    });
    if(subforums == 'true'){
        passage.forumType = 'subforum';
        await passage.save();
    }
    if(!isRoot){
        //add passage to parent sub passage list
        if(subforums == 'true'){
            parent.subforums.push(passage);
            parent.markModified('subforums');
        }
        else{
            parent.passages.push(passage);
            parent.markModified('passages');
        }
        await parent.save();
    }
    let find = await Passage.findOne({_id: passage._id}).populate('author sourceList');
    return find;
}
app.post('/create_passage/', async (req, res) => {
    if(!req.session.user){
        return res.send("Not logged in.");
    }
    let user = req.session.user || null;
    var newPassage = await createPassage(user, req.body.passageID);
    res.render('passage', {subPassages: false, passage: newPassage, sub: true});
});
async function updatePassageFunc(){

}
app.post('/create_initial_passage/', async (req, res) => {
    if(!req.session.user){
        return res.send("You must log in to create a passage.");
    }
    let user = req.session.user || null;
    //create passage
    var newPassage = await createPassage(user, req.body.chief.toString(), req.body.subforums);
    //update passage
    var formData = req.body;
    var passage = await Passage.findOne({_id: newPassage._id}).populate('author users sourceList');
    if(passage.author._id.toString() != req.session.user._id.toString()){
        return res.send("You can only update your own passages.");
    }
    else if(passage.public_daemon == 2 || passage.default_daemon){
        return res.send("Not allowed.");
    }
    switch(req.body.whichPage){
        case 'tasks':
            passage.public = true;
            break;
    }
    passage.html = formData.html;
    passage.css = formData.css;
    passage.javascript = formData.js;
    passage.title = formData.title;
    passage.content = formData.content;
    passage.tags = formData.tags;
    passage.code = formData.code;
    passage.bibliography = formData.bibliography;
    passage.lang = formData.lang;
    passage.fileStreamPath = formData.filestreampath;
    //no longer synthetic if it has been edited
    passage.synthetic = false;
    var uploadTitle = '';
    if (!req.files || Object.keys(req.files).length === 0) {
        //no files uploaded
        console.log("No files uploaded.");
        passage.filename = passage.filename;
    }
    else{
        console.log('File uploaded');
        await uploadFile(req, res, passage);
    }
    await passage.save();
    if(passage.mainFile && req.session.user.admin){
        //also update file and server
        updateFile(passage.fileStreamPath, passage.code);
    }
    passage = bubbleUpAll(passage);
    passage = await fillUsedInListSingle(passage);
    if(formData.page == 'stream'){
        return res.render('passage', {subPassages: false, passage: passage, sub: true});
    }
    else if(formData.page == 'forum' && formData.which != 'thread'){
        console.log(req.body.chief);
        passage.numViews = await scripts.getNumViews(passage._id);
        if(passage.passages && passage.passages.length > 0){
            passage.lastPost = 'by ' + passage.passages.at(-1).author.name + '<br>' + passage.passages.at(-1).date.toLocaleDateString();
        }else{
            passage.lastPost = 'No Posts Yet.';
        }
        return res.render('cat_row', {subPassages: false, topic: passage, sub: true});
    }
    else{
        return res.render('passage', {subPassages: false, passage: passage, sub: true});
    }
});
app.post('/star_passage/', async (req, res) => {
    console.log('star_passage');
    var passage_id = req.body.passage_id;
    var user = req.session.user;
    var amount = parseInt(req.body.amount);
    //get user from db
    let sessionUser = await User.findOne({_id: user._id});
    if(req.session && user){
        if(sessionUser.stars > amount && process.env.REMOTE == 'true'){
            //user must trade their own stars
            sessionUser.stars -= parseInt(amount);
            let passage = await starPassage(req, amount, req.body.passage_id, sessionUser._id, false);
            await sessionUser.save();
            return res.render('passage', {subPassages: false, passage: passage, sub: true});
        }
        else if(process.env.REMOTE == 'false'){
            let passage = await starPassage(req, amount, req.body.passage_id, sessionUser._id, false);
            await sessionUser.save();
            return res.render('passage', {subPassages: false, passage: passage, sub: true});
        }
        else{
            return res.send("Not enough stars!");
        }
    }
});
app.post('/update_passage_order/', async (req, res) => {
    let passage = await Passage.findOne({_id: req.body._id});
    // console.log(passage.public == false && req.session.user && req.session.user._id.toString() == passage.author._id.toString());
    //Only for private passages
    if(passage.public == false && req.session.user && req.session.user._id.toString() == passage.author._id.toString()){
        var passageOrder = [];
        if(typeof req.body.passageOrder != 'undefined'){
            var passageOrder = JSON.parse(req.body.passageOrder);
            let trimmedPassageOrder = passageOrder.map(str => str.trim());
            console.log(trimmedPassageOrder);
            // console.log(passage.passages[0]._id+'  '+ passage.passages[1]._id+ '  '+passage.passages[2]._id);
            passage.passages = trimmedPassageOrder;
            passage.markModified('passages');
            await passage.save();
        }
        // console.log(passageOrder);
    }
    //give back updated passage
    res.send('Done');
});
app.post('/ppe_add', async (req, res) => {
    if(!req.session.user){
        return res.send("Not logged in.");
    }
    var uploadTitle = v4();
    var data = req.body.dataURL.replace(/^data:image\/\w+;base64,/, "");
    var buf = Buffer.from(data, 'base64');
    const fsp = require('fs').promises;
    await fsp.writeFile('./dist/uploads/'+uploadTitle, buf);
    let passage = await Passage.create({
        author: req.session.user,
        users: [req.session.user],
        parent: req.body.parent == 'root' ? null: req.body.parent,
        filename: uploadTitle,
        sourceList: req.body.sourceList,
        mimeType: 'image'
    });
    var newOne = await Passage.find({_id: passage._id});
    if(req.body.parent !== 'root'){
        let Parent = await Passage.findOne({_id: req.body.parent});
        Parent.passages.push(newOne);
        await Parent.save();
    }
    let find = await Passage.findOne({_id: passage._id});
    res.render('ppe_thumbnail', {thumbnail: find});
});
app.get('/ppe_queue', async (req, res) => {
    let passages = await Passage.find({
        parent: req.query.parent == 'root' ? null : req.query.parent,
        mimeType: 'image'
    }).sort('-stars');
    return res.render('ppe_thumbnails', {thumbnails: passages});
});
app.get('/three', async (req, res) => {
    res.render('three');
});
async function getModels(data, parent=null){
    var find = {
        mimeType: 'model',
        parent: parent,
        title: {
            $regex: data.query,
            $options: 'i',
        }
    };
    let models = await Passage.paginate(find, {
        page: data.page,
        limit: DOCS_PER_PAGE,
        populate: 'author',
        sort: '-stars'
    });
    return models.docs;
}
app.get('/models', async (req, res) => {
    var models = await getModels(req.query);
    res.send(models);
});

app.get('/models/:title/:_id', async (req, res) => {
    var model = await Passage.findOne({_id: req.params._id});
    var models = await getModels(req.query, model._id);
    res.send(models);
});
async function getSVGs(data){
    var find = {
        mimeType: 'image',
        isSVG: true,
        title: {
            $regex: data.query,
            $options: 'i',
        }
    };
    let svgs = await Passage.paginate(find, {
        page: data.page,
        limit: DOCS_PER_PAGE,
        populate: 'author',
        sort: '-stars'
    });
    return svgs.docs;
}
app.get('/svgs', async (req, res) => {
    var svgs = await getSVGs(req.query);
    res.send(svgs);
});
app.post('/upload_svg', async (req, res) => {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    let obj = {};
    let search = '';
    if(req.body.username.match(regex) === null){
        //it's a username
        search = "username";
    }
    else{
        //it's an email
        search = "email";
    }
    obj[search] = req.body.username;
    User.findOne(obj)
      .exec(function (err, user) {
        if (err) {
          return callback(err)
        } else if (!user) {
          var err = new Error('User not found.');
          err.status = 401;
          return res.send("User not found.");
        }
        bcrypt.compare(req.body.password, user.password, async function (err, result) {
          if (result === true) {
            var fileToUpload = req.files.file;
            var uploadTitle = v4() + "." + fileToUpload.name.split('.').at(-1);
            fileToUpload.mv('./dist/uploads/'+uploadTitle, function(err) {
                if (err){
                    return res.status(500).send(err);
                }
            });
            var passage = await Passage.create({
                sourceList: req.body.sources,
                author: user._id,
                title: req.body.title,
                mimeType: 'model',
                filename: uploadTitle,
                thumbnail: null,
                isSVG: true
            });
            return res.send("Done");
          } else {
            return res.send("Wrong Credentials.");
          }
        })
      });
});
app.post('/update_thumbnail', async (req, res) => {
    var data = req.body.thumbnail.replace(/^data:image\/\w+;base64,/, "");
    var buf = Buffer.from(data, 'base64');
    const fsp = require('fs').promises;
    var thumbnailTitle = v4() + ".png";
    await fsp.writeFile('./dist/uploads/'+thumbnailTitle, buf);
    await Passage.findOneAndUpdate({_id: req.body.passageID}, {
        $set: {
            thumbnail: thumbnailTitle
        }
    });
    res.send("Done");
});

//for API :: blender add-on
app.post('/upload_model', async (req, res) => {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    let obj = {};
    let search = '';
    var user = await authenticateUsername(req.body.username, req.body.password);
    if(user){
        var fileToUpload = req.files.file;
        var uploadTitle = v4() + "." + fileToUpload.name.split('.').at(-1);
        fileToUpload.mv('./dist/uploads/'+uploadTitle, function(err) {
            if (err){
                return res.status(500).send(err);
            }
        });
        var passage = await Passage.create({
            sourceList: req.body.sources,
            author: user._id,
            title: req.body.title,
            mimeType: 'model',
            filename: uploadTitle,
            thumbnail: null
        });
        return res.send("Done");
    }
    else{
        return res.send("Wrong Credentials.");
    }
});
app.post('/update_metadata', async (req, res) => {
    var passage = await Passage.findOne({_id: req.body._id});
    if(req.session.user && passage.author._id.toString() == req.session.user._id.toString()){
        passage.metadata = req.body.metadata;
        await passage.save();
    }
});
//temp function for external api (mostly to work with metadata)
app.post('/passage_update', async (req, res) => {
    var passage = await Passage.findOne({_id: req.body._id}).populate('author users sourceList');
    if(req.session.user && req.session.user._id.toString() == passage.author._id.toString()){
        return res.send(await updatePassage(req.body._id, req.body.attributes));
    }
});
//attributes is an object
//temp for external api
async function updatePassage(_id, attributes){
    var passage = await Passage.findOne({_id: _id}).populate('author users sourceList');
    const keys = Object.keys(attributes);
    keys.forEach((key, index) => {
        passage[key] = attributes[key];
    });
    await passage.save();
    return 'Done';
}
app.post('/change_profile_picture/', async (req, res) => {
    await uploadProfilePhoto(req, res);
    res.redirect("/profile");
});
app.post('/update_passage/', async (req, res) => {
    var _id = req.body._id;
    var formData = req.body;
    var subforums = formData.subforums;
    var passage = await Passage.findOne({_id: _id}).populate('author users sourceList');
    if(passage.author._id.toString() != req.session.user._id.toString()){
        return res.send("You can only update your own passages.");
    }
    else if(passage.public_daemon == 2 || passage.default_daemon){
        return res.send("Not allowed.");
    }
    passage.html = formData.html;
    passage.css = formData.css;
    passage.javascript = formData.js;
    passage.title = formData.title;
    passage.content = formData.content;
    passage.tags = formData.tags;
    passage.code = formData.code;
    passage.bibliography = formData.bibliography;
    passage.lang = formData.lang;
    passage.fileStreamPath = formData.filestreampath;
    //no longer synthetic if it has been edited
    passage.synthetic = false;
    var uploadTitle = '';
    if (!req.files || Object.keys(req.files).length === 0) {
        //no files uploaded
        console.log("No files uploaded.");
        passage.filename = passage.filename;
    }
    else{
        console.log('File uploaded');
        await uploadFile(req, res, passage);
    }
    if(subforums == 'true'){
        console.log(3);
    }
    console.log("INFO: " + subforums);
    //Only for private passages
    if(passage.public == false && req.session.user && req.session.user._id.toString() == passage.author._id.toString()){
        var passageOrder = [];
        if(req.body.passageOrder != 'false' && req.body.isChief != 'false'){
            var passageOrder = JSON.parse(req.body.passageOrder);
            let trimmedPassageOrder = passageOrder.map(str => str.trim());
            console.log(trimmedPassageOrder);
            // console.log(passage.passages[0]._id+'  '+ passage.passages[1]._id+ '  '+passage.passages[2]._id);
            if(subforums == 'true'){
                console.log(2);
                passage.subforums = trimmedPassageOrder;
                passage.markModified('subforums');
            }
            else{
                passage.passages = trimmedPassageOrder;
                passage.markModified('passages');
            }
        }
        // console.log(passageOrder);
    }
    await passage.save();
    if(passage.mainFile && req.session.user.admin){
        //also update file and server
        // updateFile(passage.fileStreamPath, passage.code);
    }
    passage = bubbleUpAll(passage);
    passage = await fillUsedInListSingle(passage);
    //give back updated passage
    return res.render('passage', {subPassages: false, passage: passage, sub: true});
});
app.post('/removeFile', async (req, res) => {
    var passage = await Passage.findOne({_id: req.body._id});
    passage.filename = '';
    passage.mimeType = '';
    await passage.save();
    res.send("Done.");
});
async function uploadProfilePhoto(req, res){
    var user = await User.findOne({_id: req.session.user._id});
    if(req.files == null){
        user.thumbnail = '';
        await user.save();
    }else{
        // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file
        var fileToUpload = req.files.photo;
        //uuid with  ext
        var uploadTitle = v4() + "." + fileToUpload.name.split('.').at(-1);
        var where = 'uploads';
        // Use the mv() method to place the file somewhere on your server
        fileToUpload.mv('./dist/'+where+'/'+uploadTitle, function(err) {
            if (err){
                return res.status(500).send(err);
            }
        });
        user.thumbnail = uploadTitle;
        await user.save();
    }
}
async function deleteOldUploads(passage){
    const Passage = require('./models/Passage');
    var where = passage.personal ? 'protected' : 'uploads';
    for(const f of passage.filename){
        console.log(f);
        var passages = await Passage.find({
            filename: {
                $in: [f]
            }
        });
        if(passages.length == 1){
            if(where == 'uploads'){
                var path = './dist/'+where+'/'+f;
            }
            else{
                var path = './protected/'+f;
            }
            console.log("FILEPATH TO UNLINK:" + passage.filename);
            try{
                if(passage.filename.length > 0){
                    await fsp.unlink(path);
                    console.info(`removed old upload`);
                    var filteredArray = passage.filename.filter(e => e !== f)
                    passage.filename = filteredArray;
                }
            }
            catch(e){
                console.log(passage.filename);
                console.log(passage.filename.length);
                console.log("No file to unlink.");
                passage.filename = [];
            }
        }
    }
    await passage.save();
}
async function uploadFile(req, res, passage){
    console.log("Upload Test");
    await deleteOldUploads(passage);
    var passages = await Passage.find({}).limit(20);
    var files = req.files;
    // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file
    var fileToUpload = req.files.file;
    //check if fileToUpload is an array
    if(Array.isArray(fileToUpload)){

    }
    else{
        fileToUpload = [fileToUpload];
    }
    // passage.filename = [];
    var i = 0;
    var j = 0;
    for(const file of fileToUpload){
        var mimeType = fileToUpload[i].mimetype; 
        //uuid with  ext
        var uploadTitle = v4() + "." + fileToUpload[i].name.split('.').at(-1);
        var thumbnailTitle = v4() + ".jpg";
        var where = passage.personal ? 'protected' : 'uploads';
        if(where == 'protected'){
            var fullpath = './' + where
            var partialpath = where;
            var simplepath = where
        }
        else{
            var fullpath = './dist/' + where;
            var partialpath = 'dist/' + where;
            var simplepath = where
        }
        passage.filename[i] = uploadTitle;
        console.log("PATH:"+fullpath+'/'+uploadTitle)
        // Use the mv() method to place the file somewhere on the server
        fileToUpload[i].mv(fullpath+'/'+uploadTitle, async function(err) {
            if (err){
                console.log("DID NOT MOVE FILE");
                return res.status(500).send(err);
            }
            console.log("MOVED FILE");
            //compress if image
            if(mimeType.split('/')[0] == 'image'){
                exec('python3 compress.py '+partialpath+'/'+uploadTitle + ' ' + mimeType.split('/')[1] + ' ' + passage._id
            , async (err, stdout, stderr) => {
                    console.log(err + stdout + stderr);
                    console.log("=Ok actually finished compressing img");

                    //not enough memory on server
                    //local for now
                    if(process.env.LOCAL == 'true'){
                        exec('node nsfw.js '+where+'/'+uploadTitle + ' ' + where + ' ' + passage._id + ' image'
                        , (err, stdout, stderr) => {
                                //done
                                console.log(err + stdout + stderr);
                            });
                    }
                });
            }
            var newfilename = uploadTitle.split('.')[0]+'_c.'+uploadTitle.split('.')[1];
            if(mimeType.split('/')[0] == 'video'){
                console.log("Beginning video processing");
                var ext = newfilename.split('.').at(-1);
                var cmd = '';
                switch(ext){
                case 'webm':
                    cmd = 'ffmpeg -i '+partialpath+'/'+uploadTitle + ' -c:v libvpx -crf 18 -preset veryslow -c:a copy '+partialpath+'/'+newfilename;
                    break;
                // case 'mp4':
                //     cmd = 'ffmpeg -i dist/'+where+'/'+uploadTitle + ' -vcodec libx25 -crf 18 dist/'+where+'/'+newfilename;
                //     break;
                default:
                    cmd = 'echo "Hello, World"';
                    newfilename = uploadTitle;
                }
                exec(cmd
                , async (err, stdout, stderr) => {
                        console.log('Video compressed.' + newfilename);
                        // UNCOMMENT TO VIEW OUTPUT
                        console.log(err + stdout + stderr);

                        passage.filename[j++] = newfilename;
                        console.log("FILENAMES:" + passage.filename);
                        // update filename to compressed video
                        await Passage.findOneAndUpdate({_id: passage._id}, 
                            {$set: {
                                filename: passage.filename
                            }
                        });
                        //delete uncompressed video
                        if(newfilename != uploadTitle){
                            fs.unlink(partialpath+'/'+uploadTitle, function(err){
                                if (err && err.code == 'ENOENT') {
                                    // file doens't exist
                                    console.info("File doesn't exist, won't remove it.");
                                } else if (err) {
                                    // other errors, e.g. maybe we don't have enough permission
                                    console.error("Error occurred while trying to remove file");
                                } else {
                                    console.info(`removed`);
                                }
                            });
                        }
                        //not enough memory on server. local for now.
                        if(process.env.LOCAL == 'true'){
                            var screenshotName = v4();
                            ffmpeg(fullpath+'/'+newfilename)
                              .on('filenames', function(filenames) {
                                console.log('Will generate ' + filenames.join(', '))
                              })
                              .on('end', async function() {
                                console.log('Screenshots taken');
                                exec('node nsfw.js '+where+'/'+newfilename + ' ' + where + ' ' + passage._id + ' video ' + screenshotName
                                    , (err, stdout, stderr) => {
                                    console.log(err + stdout + stderr);
                                    console.log("Finished Processing Media.");
                                    //done
                                    //delete each screenshot
                                    for(var t = 1; t < 4; ++t){
                                      fs.unlink(partialpath + '/' + screenshotName+'_'+t + '.png', function(err2){
                                        if (err2 && err2.code == 'ENOENT') {
                                            // file doens't exist
                                            console.info("File doesn't exist, won't remove it.");
                                        } else if (err2) {
                                            // other errors, e.g. maybe we don't have enough permission
                                            console.error("Error occurred while trying to remove file");
                                        } else {
                                            console.info(`removed screenshot.`);
                                        }
                                      });
                                    }
                                });
                              })
                              .screenshots({
                                // Will take screens at 25%, 50%, 75%
                                count: 3,
                                filename: screenshotName +'_%i.png',
                                folder: partialpath
                              });
                      }
                    });
            }
        });
        if(mimeType.split('/')[0] == 'image'
        && mimeType.split('+')[0].split('/')[1] == 'svg'){
            passage.isSVG = true;
        }
        else{
            passage.isSVG = false;
        }
        passage.mimeType[i] = mimeType.split('/')[0];
        if(passage.mimeType[i] == 'model' || passage.isSVG){
            var data = req.body.thumbnail.replace(/^data:image\/\w+;base64,/, "");
            var buf = Buffer.from(data, 'base64');
            const fsp = require('fs').promises;
            await fsp.writeFile(fullpath+'/'+thumbnailTitle, buf);
            passage.thumbnail = thumbnailTitle;
        }
        else{
            passage.thumbnail = null;
        }
        i++;
        passage.markModified('filename');
        passage.markModified('mimeType');
        await passage.save();
    }
    await passage.save();
    console.log(passage.filename + "TEST");
}
app.get('/verify/:user_id/:token', function (req, res) {
    var user_id = req.params.user_id;
    var token = req.params.token;

    User.findOne({'_id': user_id.trim()}, function (err, user) {
        if (user.token == token) {
            console.log('that token is correct! Verify the user');

            User.findOneAndUpdate({'_id': user_id.trim()}, {'verified': true}, function (err, resp) {
                console.log('The user has been verified!');
            });

            res.redirect('/');
        } else {
            console.log('The token is wrong! Reject the user. token should be: ' + user.verify_token);
            res.redirect('/');
        }
    });
});

var extList = {
    'python' : '.py',
    'javascript' : '.js',
    'css' : '.css',
    'mixed' : '.html',
    'bash': '.sh',
    'ejs': '.ejs',
    'markdown': '.md',
    'rich': '.default',
    'text': '.txt'
};
//GET more extensive list
function getExt(lang){
    return extList[lang].toString();
}
function getLang(ext){
    return Object.keys(extList).find(key => extList[key] === ext);
}

//For Sasame Rex - CES Connect
class Directory {
    constructor(passage){
        this.title = encodeURIComponent(passage.title);
        //index.html (rtf from quill)
        this.code = passage.code || '';
        this.contents = [];
        this.ext = getExt(passage.lang);
    }
}
class File {
    constructor(passage){
        this.title = encodeURIComponent(passage.title);
        this.code = passage.code || '';
        this.ext = getExt(passage.lang);
    }
}

function getDirectoryStructure(passage){
    var directory = new Directory(passage);
    // populate directory recursively
    (function lambda(passages, directory){
        for(const p of passages){
            if(p.passages.length > 0){
                let dir = new Directory(p);
                directory.contents.push(dir);
                lambda(p, dir);
            }
            else{
                let file = new File(p);
                directory.contents.push(file);
            }
        }
    })(passage.passages, directory);
    return directory;
}

async function decodeDirectoryStructure(directory, location="./dist/filesystem"){
    const fsp = require('fs').promises;
    //clear filesystem
    await fsp.rmdir('./dist/filesystem', {recursive: true, force: true});
    //regenerate
    await fsp.mkdir("./dist/filesystem");
    //add new directory
    await fsp.mkdir(location + '/' + directory.title);
    await fsp.writeFile(location + '/' + directory.title + '/index' + directory.ext, directory.code);
    for(const item of directory.contents){
        if(item instanceof Directory){
            await decodeDirectoryStructure(location + '/' + directory.title, item);
        }
        else if(item instanceof File){
            var ext = item.ext;
            if(ext == 'mixed'){
                item.code = `
                <!DOCTYPE html>
                <html lang="en">
                <head>
                    <meta charset="UTF-8">
                    <meta http-equiv="X-UA-Compatible" content="IE=edge">
                    <meta name="viewport" content="width=device-width, initial-scale=1.0">
                    <title>`+item.title+`</title>
                    <style>`+item.css+`</style>
                    <script src="/jquery.min.js"></script>
                </head>
                <body>
                    <%-`+item.html+`%>
                    <script>`+item.javascript+`</script>
                </body>
                </html>
                `;
                ext = 'html';
            }
            await fsp.writeFile(location + '/' + directory.title + '/' + item.title + '.' + ext, item.code);
        }
    }
}

async function loadFileSystem(){
    var location = '/filesystem';
    //get all root passages
    var passages = await Passage.find({parent: null});
    //get all root directories
    var directories = [];
    for(const passage of passages){
        directories.push(getDirectoryStructure(passage));
    }
    //implement in filesystem
    for(const directory of directories){
        await decodeDirectoryStructure(location, directory);
    }
}

async function passageFromDirectory(filePath){

}

app.post('/install_passage', async function(req, res){
    const fsp = require('fs').promises;
    var passage = await Passage.findOne({_id: req.body._id});
    var directory = getDirectoryStructure(passage);
    await decodeDirectoryStructure(directory);
    res.send("Done")
});

app.post('/test/', async function(req, res){
    res.send("OK");
});

/*
    ROUTERS FOR FILESTREAM
*/

if(process.env.DOMAIN == 'localhost'){
    app.post('/server_eval', requiresAdmin, function(req, res) {
        eval(req.code);
    });
}
//testing
// (async function(){
  //   //TEMP: Change all mixed passages to rich
 //    await Passage.updateMany({
   //      javascript: {
    //         $ne: null
     //    },
      //   html: {
       //      $ne: null
        // },
//     }, {
 //        lang: 'mixed'
  //   });
//     await Passage.updateMany({
//         lang: 'mixed',
 //    }, {
  //       lang: 'rich'
   //  });
// })();
// (async function(){
 //    //clear filestream
//     await Passage.deleteMany({mainFile: true});
 //    //create filestream
  //   await loadFileStream();
// })();
//\testing
async function syncFileStream(){
    //clear filestream
    await Passage.updateMany({mainFile:true}, {mainFile:false});
    await Passage.deleteMany({fileStreamPath: {$ne:null}});
    var author = await User.findOne({admin:true});
    let top = await Passage.create({
        title: 'Infinity Forum Source Code',
        author: author._id,
        fileStreamPath: __dirname + '/',
        mainFile: true,
        public: false,
        parent: null,
    });
    //create filestream
    await loadFileStream(top);
}
//create FileStream passage if not exists
async function loadFileStream(top, directory=__dirname){
    const fsp = require('fs').promises;
    var author = await User.findOne({admin:true});
    try {
        const files = await readdir(directory);
        console.log(directory);
        let parentDirectory = await Passage.findOne({
            mainFile: true,
            fileStreamPath: directory
        });
        for (const file of files){
		console.log("Public = False");
            if(file == '.env' || file == '.git' || file == 'node_modules' || file == 'images'){
                continue;
            }
            // console.log(directory + '/' + file);
            //create passage for file or directory
            var stats = await fsp.stat(directory + '/' + file);
            var title = file;
            if(await stats.isDirectory()){
                //create directory passage
                var exists = await Passage.findOne({
                    mainFile: true,
                    fileStreamPath: directory + '/' + file,
                    title: title + '/'
                });
                if(exists == null){
                    let passage = await Passage.create({
                        title: title + '/',
                        author: author._id,
                        fileStreamPath: directory + '/' + file,
                        mainFile: true,
                        public: false,
                        parent: directory == __dirname ? top._id : parentDirectory._id,
                    });
                }
                //recursively create passages
                //put in parent directory
                await loadFileStream(top, directory + '/' + file);
            }
            else{
                //create passage
                var exists = await Passage.findOne({
                    mainFile: true,
                    fileStreamPath: directory + '/' + file,
                    title: file
                });
                if(exists == null){
                    let passage = await Passage.create({
                        title: title,
                        author: author._id,
                        code: await fsp.readFile(directory + '/' + file),
                        lang: getLang('.' + file.split('.').at('-1')),
                        fileStreamPath: directory + '/' + file,
                        mainFile: true,
                        parent: directory == __dirname ? top._id : parentDirectory._id,
                        public: false
                    });
                    if(parentDirectory != null){
                        parentDirectory.passages.push(passage);
                        await parentDirectory.save();
                    }
                }
                else{
                    // console.log(exists);
                }
            }
        }
    } catch (err) {
        console.log(err);
    // console.error(await err);
    }
}

app.post('/makeMainFile', requiresAdmin, async function(req, res){
    var passage = await Passage.findOne({_id: req.body.passageID});
    var passage = bubbleUpAll(passage);
    //check if file/dir already exists
    var exists = await Passage.findOne({fileStreamPath: req.body.fileStreamPath});
    if(exists != null){
        exists.mainFile = false;
        await exists.save();
    }
    passage.mainFile = true;
    await passage.save();
    //restart server to apply changes
    updateFile(req.body.fileStreamPath, passage.all);
});
app.get('/testing', async function(req, res){
    res.render('testing');
});
app.get('/filestream/:viewMainFile?/:directory?', async function(req, res){
    const ISMOBILE = browser(req.headers['user-agent']).mobile;
    //output passages in directory / or req.body.directory
    var directory = req.params.directory || __dirname;
    //get passages where fileStreamPath starts with directory
    var viewMainFile;
    if(req.params.viewMainFile === 'false'){
        viewMainFile = false;
    }
    else if(req.params.viewMainFile === 'true'){
        viewMainFile = true;
    }
    else{
        viewMainFile = true;
    }
    var passages;
    if(viewMainFile){
        passages = await Passage.find({
            fileStreamPath: {
                $regex: '^' + directory + '/[^/]*(/?)$',
                $options: 'i'
            },
            mainFile: viewMainFile
        }).collation({locale: 'en', strength: 2}).sort({title: 1}); //sort alphabetically
    }
    else{
        //there may be duplicates so sort by stars
        passages = await Passage.find({
            fileStreamPath: {
                $regex: '^' + directory + '/[^/]*(/[^/]*)?$',
                $options: 'i'
            },
            // mainFile: viewMainFile
        }).sort({stars: '-1'}).limit(10);
    }
    let bookmarks = [];
    // if(req.session.user){
    //     bookmarks = await User.find({_id: req.session.user._id}).populate('bookmarks').passages;
    // }
    if(req.session.user){
        bookmarks = getBookmarks(req.session.user);
    }
    for(const passage of passages){
        passages[passage] = bubbleUpAll(passage);
        passage.location = await returnPassageLocation(passage);
    }
    passages = await fillUsedInList(passages);
    res.render("filestream", {
        subPassages: false,
        passageTitle: false, 
        scripts: scripts, 
        passages: passages, 
        mainFiles: viewMainFile,
        passage: {id:'root', author: {
            _id: 'root',
            username: 'Sasame'
        }},
        bookmarks: bookmarks,
        ISMOBILE: ISMOBILE
    });
    // return res.render('passages', {
    //     passages: passages,
    //     subPassages: false
    // });
    //on directory click just run same route with different directory
    
});
app.post('/syncfilestream', requiresAdmin, async function(req, res){
    await syncFileStream();
    res.send("Done.");
});
app.post('/updateFileStream', requiresAdmin, async function(req, res) {
    var passage = await Passage.findOne({_id: req.body.passageID});
    passage.fileStreamPath = req.body.fileStreamPath;
    await passage.save();
    //create file if not exists
    //else update
    //check if directory or file
    var isDirectory = false;
    var isFile = false;
    if(passage.fileStreamPath.at(-1) == '/'){
        isDirectory = true;
    }
    else{
        isFile = true;
    }
    const fsp = require('fs').promises;
    //TODO: check if need to check for exists or if fsp handles this
    if(isDirectory){
        await fsp.mkdir(__dirname + passage.fileStreamPath);
    }
    else if(isFile){
        await fsp.writeFile(__dirname + passage.fileStreamPath);
    }
});
app.post('/run_file', requiresAdmin, function(req, res) {
    var file = req.body.file;
    var ext = file.split('.')[file.split('.').length - 1];
    var bash = 'ls';
    switch(ext){
        case 'js':
        bash = 'node ' + file;
        break;
        case 'sh':
        bash = 'sh ' + file;
        break;
    }
    exec(bash, (err, stdout, stderr) => {
      if (err) {
        // node couldn't execute the command
        res.send(JSON.stringify(err));
        return;
      }
      res.send(stdout);
      // the *entire* stdout and stderr (buffered)
      // console.log(`stdout: ${stdout}`);
      // console.log(`stderr: ${stderr}`);
    });
});
function updateFile(file, content){
    fs.writeFile(file, content, function(err){
        if (err) return console.log(err);
        //restart server to apply changes
        //happens after write on dev
        if(process.env.REMOTE){
            var shell = require('shelljs');
                var bash = 'sh ' + __dirname + 'restart.sh';
            shell.exec(bash, function(code, output) {
            console.log('Exit code:', code);
            console.log('Program output:', output);
            });
        //restart.sh (Server file)
        //echo "password" | sudo pm2 restart sasame
        // echo "password" | sudo systemctl restart nginx

        }
    });
}
app.post('/update_file', requiresAdmin, function(req, res) {
    var file = req.body.file;
    var content = req.body.content;
    fs.writeFile(file, content, function(err){
      if (err) return console.log(err);
      res.send('Done');
    });
});
app.get('/terms', function(req, res) {
    res.render('terms');
});
app.get('/protected/:filename', async function(req, res) {
    if(!req.session.user){
        return res.redirect('/');
    }
    var passages = await Passage.find({
        filename: {
            $in: [req.params.filename]
        }
    });
    var clear = false;
    for(const p of passages){
        if(p.author._id.toString() == req.session.user._id.toString()
            || p.users.includes(req.session.user._id)){
            clear = true;
            break;
        }
    }
    if(clear){
        switch(req.params.filename.split('.').at(-1)){
            case 'png':
                res.type('image/png');
                break;
            case 'webm':
                res.type('video/webm');
                break;
        }
        return res.sendFile('protected/'+req.params.filename, {root: __dirname});
    }
    else{
        return res.redirect('/');
    }
});
//API Funcs for returning objects directly
//TODO: Just check for api parameter in original routes
//for daemons to get passages
app.get('/findOne', async function(req, res) {
    return res.send(await Passage.findOne({_id: req.query._id}));
});
//FUNCTIONS
//authenticate input against database
function authenticateUser(email, password, callback) {
  User.findOne({ email: email })
    .exec(function (err, user) {
      if (err) {
        return callback(err)
      } else if (!user) {
        var err = new Error('User not found.');
        err.status = 401;
        return callback(err);
      }
      bcrypt.compare(password, user.password, function (err, result) {
        if (result === true) {
          return callback(err, user);
        } else {
          return callback(err);
        }
      })
    });
}
async function authenticateUsername(username, password){
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    let obj = {};
    let search = '';
    if(username.match(regex) === null){
        //it's a username
        search = "username";
    }
    else{
        //it's an email
        search = "email";
    }
    obj[search] = username;
    var user = await User.findOne(obj);
    if(!user){
        return false;
    }
    var result = await bcrypt.compare(password, user.password);
    if(result === true){
        return user;
    }
    return false;
}
async function requiresLogin(req, res, next){
    if(req.session.user){
        next();
    }
    else{
        return res.redirect('/loginform');
    }
}
async function requiresAdmin(req, res, next){
    if(req.session.user && req.session.user.admin){
        next();
    }
    else{
        return res.redirect('/');
    }
}
function sendEmail(to, subject, body){
    var transporter = nodemailer.createTransport({
      service: 'gmail',
      auth: {
        user: process.env.EMAIL_USERNAME,
        pass: process.env.EMAIL_PASSWORD
      }
    });

    var mailOptions = {
      from: 'Infinity-Forum.org',
      to: to,
      subject: subject,
      text: body
    };

    transporter.sendMail(mailOptions, function(error, info){
      if (error) {
        console.log(error);
      } else {
        console.log('Email sent: ' + info.response);
      }
    });
}

//AI
// (async function(){
//     await PrimeEngine();
// })();
//run every minute
// cron.schedule('* * * * *', async () => {
//     await PrimeEngine();
// });
//clean engine every 10 minutes
// cron.schedule('*/10 * * * *', async () => {
//     await cleanEngine();
//     console.log('Cleaned AI.');
// });
//return synthetically annealled random passage
//in other words, bias towards a greater number of stars
async function anneal(){
    var numDaemons = await Passage.countDocuments();
    //bias towards later records (more stars)
    var random1 = Math.floor(Math.random() * numDaemons * 3.3333333);
    //go back some
    var random2 = Math.floor(Math.random() * numDaemons);
    var slide = random1 - random2;
    if(slide >= numDaemons){
        slide = numDaemons - 1;
    }
    else if(slide < 0){
        slide = 0;
    }
    return slide;
}
//just treats all passages as daemons
async function PrimeEngine(){
    var passage;
    //Get random passage from database
    var numDaemons = await Passage.countDocuments();
    // Get a random entry
    // var random = Math.floor(Math.random() * numDaemons);
    var slide = await anneal();
    var passage = await Passage.findOne().sort('stars').skip(slide).exec();
    //modify daemon with another daemon
    var modifiedPassage = await BetaEngine(passage);
    console.log('Synthetic Passage Created: ' + modifiedPassage.title);
}

//beta engine makes passage into daemon and feeds PrimeEngine
//anneal by rank
async function BetaEngine(original){
    var titleEnd = " - Sasame AI";
    var author = await User.findOne({admin:true});
    //get random passage as daemon
    var numDaemons = await Passage.countDocuments();
    // Get a random entry
    var slide = await anneal();
    // var random = Math.floor(Math.random() * numDaemons);
    var daemon = await Passage.findOne().sort('stars').skip(slide).exec();
    //personalize daemon to affect target passage
    var personalDaemon = await passageController.copyPassage(daemon, [author], null, async function(){
        
    }, true);
    personalDaemon.synthetic = true;
    personalDaemon.param = JSON.stringify(original);
    personalDaemon.title = personalDaemon.title.split(' - Sasame AI')[0] + titleEnd;
    var paramTitle = '';
    if(JSON.parse(personalDaemon.param) != null){
        paramTitle += JSON.parse(personalDaemon.param).title;
    }
    //might need to stringify personalDaemon.params
    //anyway; this makes it easy for a daemon to access its parameters
    //then, might I suggest NOHTML?
    personalDaemon.libs = 'const PARAMTITLE =   "'+paramTitle+'";\n';
    personalDaemon.libs +=  'const PARAM = '+personalDaemon.param+';\n'; //wont show in editor (long) but they can access the var
    personalDaemon.javascript = '//const PARAMTITLE = "'+paramTitle+'";\n// ex. var paramDetails = JSON.stringify(PARAM); // (PARAM is a passage)\n' + (personalDaemon.javascript || '');
    //ex. var button = params[0].title; //make button from param
    await personalDaemon.save();
    //in iframe will be a modification of the original passage
    return personalDaemon;
}
async function cleanEngine(){
    //delete all synthetic passage with 0 stars
    //and all sub passages
    await Passage.deleteMany({synthetic: true, stars: 0});
    console.log("Cleaned AI.");
}
//remove all passages with 0 stars, and no sub passages within a public parent or root
async function filterPassages(){
    await Passage.deleteMany({
        stars: 0,
        parent: null,
        passages: null
    });
    var publics = await Passage.find({
        public: true
    });
    for(const passage of publics){
        if(passage.stars == 0){
            await Passage.deleteOne({
                _id: passage._id
            });
        }
    }
}
async function optimizeEngine(){
    //delete bottom 20% of passages
    //...
    return;
}
// cleanEngine();

//run on passage update if content chsnges
async function propagatePassage(passageID){
    var passage = Passage.findOne({_id: passsageID}).populate('input');
    //if lang = rich then content
    //else code
    var output;
    if(passage.lang == 'rich'){
        output = passage.content;
    }
    else{
        output = passage.code;
    }
    var input = passage.input;
    var inputs = [];
    //get outputs of all inputs
    for(const i of input){
        inputs.push(i.final);
    }
    var j = 1;
    //input1-x are protected terms
    for(const o of inputs){
        output.replace('input' + j, o);
        ++j;
    }
    passage.final = output;
    await passage.save();
    //propagate passage for each passage using this passage as an input
    var passages = Passage.find({
        //find if in array
        input: {
            $in: [passage]
        }
    });
    for(const p of passages){
        await propagatePassage(p._id);
    }
}


//SOCKETS 
io.on('connection', async (socket) => {
    socket.join('root');
    console.log('A user connected');
    //set room from client by sending passageID
    socket.on('controlPassage', async (passageID) => {
        console.log('works');
        // console.log(socket.handshake.session);
        var passage = await Passage.findOne({_id: passageID});
        socket.leave('root');
        socket.join(passage._id.toString());
        await User.findOneAndUpdate({_id: socket.handshake.session.user._id.toString()}, {$set: {room: passage._id.toString()}});
        io.sockets.in(passage._id.toString()).emit(passage._id.toString(), "Room for Passage: " + passage.title);
    });
    //send messages to room from client
    socket.on('add', async (msg) => {
        var user = await User.findOne({_id: socket.handshake.session.user._id.toString()});
        var room = user.room;
        console.log(room);
        io.sockets.in(room).emit(room, user.name + ': ' + msg);

    });
    socket.on('disconnect', function () {
        console.log('A user disconnected');
    });
});

// CLOSING LOGIC
server.listen(PORT, () => {
    console.log(`Sasame started on Port ${PORT}`);
    io.sockets.emit("serverRestart", "Test");
});
process.on('uncaughtException', function(err){
    console.log('uncaughtExceptionError ' + err);
    console.log(err);
    // server.close();
});
process.on('SIGTERM', function(err){
    console.log('SIGTERM');
    console.log(err);
    server.close();
});

//debugging
/**
 * 
(async function(){
	var passage = await GETPASSAGE('63faabffa5dc86b7e4d28180');
	document.write(passage);
})();

*/

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
#Bash scripts to help with some stuff
#More for reading and pasting right now than running

# Installing and configuration here

# git clone
# npm install
# install mongod
# sudo systemctl start mongod
# npx nodemon
# pm2/nginx for production

#########################################################################

# Helper Scripts here

# Minify JS/CSS

# Stripe Webhook testing

#stripe listen --forward-to localhost:3000/stripe_webhook

vim /etc/nginx/nginx.conf

client_max_body_size 500M; #in http bracket

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
(async function(){
	const axios = require("axios");
  	const https = require('https');
  	 axios.create({
            httpsAgent: new https.Agent({keepAlive: true}),
        });
  	 const pic = await axios.get('http://localhost:3000/protected/0f7b2b11-fad0-4eb1-b38f-0af9b7e39028_1.png', {
      responseType: "arraybuffer",
    });
  	 console.log(pic.data);
})();

0 Stars  
ADMIN
One Way , 8/8/2024
   Project in Projects/Source Code
0 Stars