site

Website's source files.
Log | Files | Refs | Submodules | LICENSE

server.ts (5998B)


      1 // Cropyright (C) Ryan Jeffrey 2022
      2 // A simple express server that uses handlebars.
      3 
      4 import express from 'express';
      5 import path from 'path';
      6 import exphbs, { engine } from 'express-handlebars';
      7 import fs from 'fs';
      8 
      9 const app = express();
     10 const port = process.env.PORT || 3000;
     11 
     12 function getMonthByNumber(i: number) : string {
     13     const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
     14                      'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
     15     return (i in months) ? months[i] : "";
     16 }
     17 
     18 function permissionToString(i: number) : string {
     19     // Unix file permission array. The mode is the index in the array.
     20     const permStrings = ['---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx'];
     21     return (i in permStrings) ? permStrings[i] : "";
     22 }
     23 
     24 class LSStat {
     25     perms: string;
     26     numLinks: number;
     27     fileSize: number;
     28     mtime: string;
     29     basename: string;
     30     
     31     constructor(thePath: string) {
     32         let stats = fs.statSync(thePath);
     33 
     34         // ls file permissions. Convert into an easier format to use.
     35         if(!stats) {
     36             console.error("Could not stat", thePath);
     37             return;
     38         }
     39         // Convert mode to string.
     40         let unixFilePermissions = (stats.mode & parseInt('777', 8)).toString(8);
     41         let permsResult = permissionToString(parseInt(unixFilePermissions[0]));
     42         permsResult += permissionToString(parseInt(unixFilePermissions[1]));
     43         permsResult += permissionToString(parseInt(unixFilePermissions[2]));
     44 
     45         let prefixChar = '-';
     46         if(stats.isDirectory()) {
     47             prefixChar = 'd';
     48         }
     49 
     50         this.perms = `${prefixChar}${permsResult}`;
     51         this.numLinks = stats.nlink;
     52         this.fileSize = stats.size;
     53         this.mtime = LSStat.lsTime(stats.mtimeMs);
     54         this.basename = thePath;
     55     }
     56 
     57     // Get the mtime in the same format that LS would.
     58     static lsTime(timeMS: number) : string {
     59         let fileDate = new Date(timeMS);
     60         let addString = "";
     61         // If the file was updated this year then set the last column to the
     62         // hour and minute. Else, the last column should be the year.
     63         if((new Date()).getFullYear() != fileDate.getFullYear())
     64             addString = `${fileDate.getHours()}:${fileDate.getMinutes()}`;
     65         else
     66             addString = ` ${fileDate.getFullYear()}`;
     67         return `${getMonthByNumber(fileDate.getMonth())} ${fileDate.getDate()}  ${addString}`;
     68     }
     69 
     70     static lsList(theDir: string, ext: string, files: string[]) : LSStat[] {
     71         let fileStats: LSStat[] = [];
     72         files.forEach((element: string) => {
     73             fileStats.push(new LSStat(path.join(theDir, element + ext)));
     74         });
     75         return fileStats;
     76     }
     77 
     78     static lsDir(thePath: string) : LSStat[] {
     79         let fileStats: LSStat[] = [];
     80         // TODO error checking.
     81         let files = fs.readdirSync(thePath);
     82 
     83         files.forEach((file) => {
     84             fileStats.push(new LSStat(file));
     85         });
     86 
     87         return fileStats;
     88     }
     89 
     90     static fileExistsIn(thePath: string, statList: LSStat[]) : boolean {
     91         for(let i = 0; i < statList.length; i++) {
     92             if(statList[i].basename == thePath)
     93                 return true;
     94         }
     95         return false;
     96     }
     97 }
     98 
     99 // Dummy class just for inheritance.
    100 class Command {
    101     args: string;
    102     constructor(args: string) {
    103         this.args = args;
    104     }
    105 }
    106 
    107 class LS extends Command {
    108     lsList: LSStat[];
    109 
    110     constructor(dir: string, ext:string, names: string[]) {
    111         super(dir);
    112         this.lsList = LSStat.lsList(dir, ext, names);
    113     }
    114 }
    115 
    116 class Cat extends Command {
    117     markup: string;
    118 
    119     constructor(path: string) {
    120         super(path);
    121         this.markup = fs.readFileSync(path, 'utf8');
    122     }
    123 }
    124 
    125 class TerminalWindow {
    126     commands: Command[];
    127 
    128     constructor(...commands: Command[]) {
    129         this.commands = commands;
    130     }
    131 }
    132 
    133 // App config
    134 
    135 app.engine('handlebars', engine({ defaultLayout: 'main' }));
    136 app.set('view engine', 'handlebars');
    137 app.set('views', "./views");
    138 
    139 app.use(express.static(path.join(process.cwd(), 'public')));
    140 app.use(express.static(path.join(process.cwd(), 'external')));
    141 app.use(express.json());
    142 
    143 // TODO maybe a system that exports org to handlebars.
    144 const postItems = LSStat.lsDir('posts');
    145 // Get the requested post
    146 app.get('/posts/:post', (req, res, next) => {
    147     let post = req.params.post.toLowerCase();
    148     if(LSStat.fileExistsIn(post, postItems)) {
    149         res.status(200).render('writing', { text : fs.readFileSync(post) });
    150     }
    151     else {
    152         // Page not found.
    153         res.status(404).render('404');
    154     }
    155 });
    156 // Posts index file.
    157 app.get('/posts', (req, res, next) => {
    158     res.status(200).render('indexWriting');
    159 });
    160 // index.html should be before 404 and after everything else
    161 // Generate files object.
    162 const files = LSStat.lsDir('public/files');
    163 // Server entry for files.
    164 app.get('/files', (req, res, next) => {
    165     res.status(200).render('files', {
    166         entries: files
    167     });
    168 });
    169 
    170 // LS everything.
    171 const frontPageItems = LSStat.lsList('.', '.html', ['main', 'software', 'sneed']);
    172 
    173 app.get('/:item', (req, res, next) => {
    174     let item = req.params.item.toLowerCase();
    175     if(LSStat.fileExistsIn(item, frontPageItems)) {
    176         res.status(200).render('post', { text : fs.readFileSync(item) });
    177     }
    178     else {
    179         // Page not found.
    180         res.status(404).render('404');
    181     }
    182 });
    183 
    184 // TODO!!!! each window needs multiple commands.
    185 app.get('/', (req, res, next) => {
    186     res.status(200).render('index', {
    187         windows: [new TerminalWindow(new Cat('public/figlet.html'),
    188                                      new LS('.', '.html', ['main', 'software', 'sneed']),
    189                                      new Cat('public/front.html'))],
    190     });
    191 });
    192 
    193 // 404 is last.
    194 app.get('*', (req, res) => {
    195     res.status(404).render('404');
    196 });
    197 
    198 // Server initialize.
    199 app.listen(port, () => {
    200     console.log('== Server is listening on port', port,
    201                 'in current directory', process.cwd());
    202 });