Add in support for shortening links.
This commit is contained in:
		
							parent
							
								
									a2140625d4
								
							
						
					
					
						commit
						d522eaadb2
					
				
					 6 changed files with 1062 additions and 25 deletions
				
			
		| 
						 | 
					@ -1,3 +1,6 @@
 | 
				
			||||||
node_modules/
 | 
					 | 
				
			||||||
.env
 | 
					.env
 | 
				
			||||||
uploads
 | 
					uploads
 | 
				
			||||||
 | 
					node_modules
 | 
				
			||||||
 | 
					docker-compose.yml
 | 
				
			||||||
 | 
					Dockerfile
 | 
				
			||||||
 | 
					data
 | 
				
			||||||
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -2,3 +2,4 @@
 | 
				
			||||||
uploads
 | 
					uploads
 | 
				
			||||||
node_modules
 | 
					node_modules
 | 
				
			||||||
docker-compose.yml
 | 
					docker-compose.yml
 | 
				
			||||||
 | 
					data
 | 
				
			||||||
| 
						 | 
					@ -13,4 +13,5 @@ services:
 | 
				
			||||||
      UPLOAD_DIR: /app/uploads    # Container path for file uploads
 | 
					      UPLOAD_DIR: /app/uploads    # Container path for file uploads
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - ./uploads:/app/uploads    # Binds the local "uploads" directory to UPLOAD_DIR
 | 
					      - ./uploads:/app/uploads    # Binds the local "uploads" directory to UPLOAD_DIR
 | 
				
			||||||
 | 
					      - ./data:/app/data          # Binds the local "data" directory to /app/data
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
							
								
								
									
										111
									
								
								index.js
									
										
									
									
									
								
							
							
						
						
									
										111
									
								
								index.js
									
										
									
									
									
								
							| 
						 | 
					@ -1,18 +1,22 @@
 | 
				
			||||||
import Fastify from 'fastify'
 | 
					// Node.js built-in modules
 | 
				
			||||||
import fastifyMultipart from '@fastify/multipart'
 | 
					import fs, { createWriteStream } from 'node:fs'
 | 
				
			||||||
import dotenv from 'dotenv'
 | 
					import path from 'node:path'
 | 
				
			||||||
import fs from 'node:fs'
 | 
					 | 
				
			||||||
import mime from 'mime-types'
 | 
					 | 
				
			||||||
import path, { join } from 'node:path'
 | 
					 | 
				
			||||||
import { createWriteStream } from 'node:fs'
 | 
					 | 
				
			||||||
import { pipeline } from 'node:stream/promises'
 | 
					 | 
				
			||||||
import sanitize from 'sanitize-filename'
 | 
					 | 
				
			||||||
import fastifyStatic from '@fastify/static'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { exec } from 'child_process'
 | 
					import { exec } from 'child_process'
 | 
				
			||||||
import { promisify } from 'node:util'
 | 
					import { promisify } from 'node:util'
 | 
				
			||||||
 | 
					import { pipeline } from 'node:stream/promises'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Third-party modules
 | 
				
			||||||
 | 
					import dotenv from 'dotenv'
 | 
				
			||||||
 | 
					import Fastify from 'fastify'
 | 
				
			||||||
 | 
					import fastifyMultipart from '@fastify/multipart'
 | 
				
			||||||
 | 
					import fastifyStatic from '@fastify/static'
 | 
				
			||||||
 | 
					import mime from 'mime-types'
 | 
				
			||||||
 | 
					import sanitize from 'sanitize-filename'
 | 
				
			||||||
import sharp from 'sharp'
 | 
					import sharp from 'sharp'
 | 
				
			||||||
 | 
					import sqlite3 from 'sqlite3'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dbFile = path.join("data", 'database.db')
 | 
				
			||||||
 | 
					const db = new sqlite3.Database(dbFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const execAsync = promisify(exec)
 | 
					const execAsync = promisify(exec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,12 +50,21 @@ fastify.get('/music.jpg', async (request, reply) => {
 | 
				
			||||||
    reply.header('Content-Type', 'image/jpeg').send(musicImage)
 | 
					    reply.header('Content-Type', 'image/jpeg').send(musicImage)
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const assetSizeCache = new Map()
 | 
					//Find the width and height of a file either an image or video, and cache the result in the db
 | 
				
			||||||
 | 
					 | 
				
			||||||
//Find the width and height of a file either an image or video, and cache the result
 | 
					 | 
				
			||||||
async function getAssetSize(filePath) {
 | 
					async function getAssetSize(filePath) {
 | 
				
			||||||
    if (assetSizeCache.has(filePath)) {
 | 
					    //Check if the asset size is already in the cache
 | 
				
			||||||
        return assetSizeCache.get(filePath)
 | 
					    const cachedSize = await new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					        db.get('SELECT * FROM asset_size_cache WHERE path = ?', [filePath], (err, row) => {
 | 
				
			||||||
 | 
					            if (err) {
 | 
				
			||||||
 | 
					                reject(err)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                resolve(row)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (cachedSize) {
 | 
				
			||||||
 | 
					        return { width: cachedSize.width, height: cachedSize.height }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const mimeType = mime.lookup(filePath) || 'application/octet-stream'
 | 
					    const mimeType = mime.lookup(filePath) || 'application/octet-stream'
 | 
				
			||||||
| 
						 | 
					@ -77,7 +90,8 @@ async function getAssetSize(filePath) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assetSizeCache.set(filePath, assetSize)
 | 
					    //Cache the result
 | 
				
			||||||
 | 
					    db.run('INSERT INTO asset_size_cache (path, width, height) VALUES (?, ?, ?)', [filePath, assetSize.width, assetSize.height])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return assetSize
 | 
					    return assetSize
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -158,6 +172,27 @@ fastify.get('/s/:date/:filename', async (request, reply) => {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Route which will redirect to the original URL
 | 
				
			||||||
 | 
					fastify.get('/l/:shortId', async (request, reply) => {
 | 
				
			||||||
 | 
					    const { shortId } = request.params
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const url = await new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					        db.get('SELECT url FROM short_urls WHERE short_id = ?', [shortId], (err, row) => {
 | 
				
			||||||
 | 
					            if (err) {
 | 
				
			||||||
 | 
					                reject(err)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                resolve(row)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (url) {
 | 
				
			||||||
 | 
					        reply.redirect(url.url)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        reply.code(404).send({ error: 'URL not found' })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sizeLimit = process.env.FILE_SIZE_LIMIT || 16; //Size limit in GB
 | 
					const sizeLimit = process.env.FILE_SIZE_LIMIT || 16; //Size limit in GB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Register multipart support
 | 
					// Register multipart support
 | 
				
			||||||
| 
						 | 
					@ -220,9 +255,51 @@ fastify.put('/upload', {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Route which will shorten a URL
 | 
				
			||||||
 | 
					fastify.put('/shorten', {
 | 
				
			||||||
 | 
					    preHandler: async (request, reply) => {
 | 
				
			||||||
 | 
					        const apiKey = request.headers.authorization
 | 
				
			||||||
 | 
					        if (!apiKey || apiKey !== process.env.API_KEY) {
 | 
				
			||||||
 | 
					            reply.code(401).send({ error: 'Unauthorized' })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}, async (request, reply) => {
 | 
				
			||||||
 | 
					    const { url } = request.body
 | 
				
			||||||
 | 
					    if (!url) {
 | 
				
			||||||
 | 
					        reply.code(400).send({ error: 'No URL provided' })
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //Short URL should be base62, 8 characters long
 | 
				
			||||||
 | 
					    const shortId = Math.random().toString(36).substring(2, 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //Insert the short URL into the database
 | 
				
			||||||
 | 
					    db.run('INSERT INTO short_urls (short_id, url) VALUES (?, ?)', [shortId, url])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const host = process.env.HOST || ''
 | 
				
			||||||
 | 
					    reply.code(200).send({ url: host.replace(/\/$/, '') + '/l/' + shortId })
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const initDb = async () => {
 | 
				
			||||||
 | 
					    //Create asset_size_cache table
 | 
				
			||||||
 | 
					    await db.run(`CREATE TABLE IF NOT EXISTS asset_size_cache (
 | 
				
			||||||
 | 
					        path TEXT PRIMARY KEY,
 | 
				
			||||||
 | 
					        width INTEGER,
 | 
				
			||||||
 | 
					        height INTEGER
 | 
				
			||||||
 | 
					    )`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //Create short_urls table
 | 
				
			||||||
 | 
					    await db.run(`CREATE TABLE IF NOT EXISTS short_urls (
 | 
				
			||||||
 | 
					        short_id TEXT PRIMARY KEY,
 | 
				
			||||||
 | 
					        url TEXT
 | 
				
			||||||
 | 
					    )`);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Start server
 | 
					// Start server
 | 
				
			||||||
const start = async () => {
 | 
					const start = async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 | 
					        await initDb();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await fastify.listen({ host: process.env.LISTEN_HOST || '0.0.0.0', port: process.env.LISTEN_PORT || 3000 })
 | 
					        await fastify.listen({ host: process.env.LISTEN_HOST || '0.0.0.0', port: process.env.LISTEN_PORT || 3000 })
 | 
				
			||||||
    } catch (err) {
 | 
					    } catch (err) {
 | 
				
			||||||
        fastify.log.error(err)
 | 
					        fastify.log.error(err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,12 +16,14 @@
 | 
				
			||||||
    "fastify": "^5.2.1",
 | 
					    "fastify": "^5.2.1",
 | 
				
			||||||
    "mime-types": "^2.1.35",
 | 
					    "mime-types": "^2.1.35",
 | 
				
			||||||
    "sanitize-filename": "^1.6.3",
 | 
					    "sanitize-filename": "^1.6.3",
 | 
				
			||||||
    "sharp": "^0.33.5"
 | 
					    "sharp": "^0.33.5",
 | 
				
			||||||
 | 
					    "sqlite3": "^5.1.7"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "type": "module",
 | 
					  "type": "module",
 | 
				
			||||||
  "pnpm": {
 | 
					  "pnpm": {
 | 
				
			||||||
    "onlyBuiltDependencies": [
 | 
					    "onlyBuiltDependencies": [
 | 
				
			||||||
      "sharp"
 | 
					      "sharp",
 | 
				
			||||||
 | 
					      "sqlite3"
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										953
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										953
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue