diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8184527 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# top level +.vscode +.DS_Store +.git + +# shared backend and frontend +*/.DS_Store +*/node_modules +*/package-lock.json +*/.env + +# backend +backend/data/anki/decks/* diff --git a/backend/app.js b/backend/app.js new file mode 100644 index 0000000..d594f90 --- /dev/null +++ b/backend/app.js @@ -0,0 +1,43 @@ +var createError = require('http-errors'); +var express = require('express'); +var path = require('path'); +var cookieParser = require('cookie-parser'); +var logger = require('morgan'); + +var indexRouter = require('./routes/index'); +var usersRouter = require('./routes/users'); +var photosearch = require('./routes/photosearch'); + +var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'jade'); + +app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', indexRouter); +app.use('/users', usersRouter); +app.use('/search/photos', photosearch) + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + next(createError(404)); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +module.exports = app; diff --git a/backend/bin/www b/backend/bin/www new file mode 100755 index 0000000..192c6f3 --- /dev/null +++ b/backend/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('backend:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/backend/lib/anki.js b/backend/lib/anki.js new file mode 100644 index 0000000..32f789f --- /dev/null +++ b/backend/lib/anki.js @@ -0,0 +1,89 @@ +import { readFile } from 'fs/promises' +import { open } from 'sqlite' +import sqlite3 from 'sqlite3' +import AdmZip from 'adm-zip' + +// database structure: https://github.com/ankidroid/Anki-Android/wiki/Database-Structure +// python example: https://github.com/patarapolw/ankisync2 +// old js example: https://github.com/nornagon/mkanki/blob/master/index.js + +// to create a zip file +// const zip = new AdmZip() +// zip.addLocalFolder('./folder_to_zip') +// zip.writeZip('./output.zip') + +class Deck { + + /** + * + * @param info ID of internal deck, or filename or anki export + * @param process whether or not this is an external anki export to process + */ + constructor(info, process) { + // pull out variables + this.notes = null // this is what is edited; may create multiple cards + this.media = null // image and audio files attached to cards/notes + } + + /** + * Loads an internal deck + */ + async load(id) { + // open database + // open media files + } + + /** + * Processes an anki export for internal use. Currently only supports + * .apkg files. In the future, may support: + * colpkg + * txt (notes) + * txt (cards) + * + * @param filename path of file to process + */ + async process(filename) { + return await this.processApkg(filename) + } + + /** + * Processes a .apkg file into a database + * + * @param filename name of file to open and process + */ + async processApkg(filename) { + // error handling + if (!filename) throw new Error('Must specify a .apkg filename') + + // setup + let uuid = crypto.randomUUID() + let location = `../data/anki/decks/${uuid}/` + + // make the resource folder if it does not exist already + // let resource = '' + // if (!fs.existsSync(resource)) fs.mkdirSync(resource, { recursive: true }) + + // extract zip + // https://stuk.github.io/jszip/documentation/howto/read_zip.html#in-nodejs + let zip = new AdmZip(filename) + zip.extractAllTo(location, true) + + // extract the notes database + let db = await open({ + filename: location + 'collection.anki2', + driver: sqlite3.cached.Database + }) + + let result = await db.get('SELECT sfld FROM notes WHERE id = :n', 1486785585103) + + console.log(result); + + // extract media files + // ? process/rename media files and update in db? + // load data into this obj + + // return { notes, media } + } +} + +export default Deck diff --git a/backend/lib/test.apkg b/backend/lib/test.apkg new file mode 100644 index 0000000..32a3241 Binary files /dev/null and b/backend/lib/test.apkg differ diff --git a/backend/lib/test.js b/backend/lib/test.js new file mode 100644 index 0000000..8a31381 --- /dev/null +++ b/backend/lib/test.js @@ -0,0 +1,4 @@ +import Deck from './anki.js' + +let deck = new Deck() +await deck.process('test.apkg'); diff --git a/backend/lib/test/0 b/backend/lib/test/0 new file mode 100644 index 0000000..13ce7a6 Binary files /dev/null and b/backend/lib/test/0 differ diff --git a/backend/lib/test/1 b/backend/lib/test/1 new file mode 100644 index 0000000..5df80ed Binary files /dev/null and b/backend/lib/test/1 differ diff --git a/backend/lib/test/10 b/backend/lib/test/10 new file mode 100644 index 0000000..9222aba Binary files /dev/null and b/backend/lib/test/10 differ diff --git a/backend/lib/test/11 b/backend/lib/test/11 new file mode 100644 index 0000000..055d4e0 Binary files /dev/null and b/backend/lib/test/11 differ diff --git a/backend/lib/test/12 b/backend/lib/test/12 new file mode 100644 index 0000000..b8d0d9c Binary files /dev/null and b/backend/lib/test/12 differ diff --git a/backend/lib/test/13 b/backend/lib/test/13 new file mode 100644 index 0000000..74f72e0 Binary files /dev/null and b/backend/lib/test/13 differ diff --git a/backend/lib/test/14 b/backend/lib/test/14 new file mode 100644 index 0000000..c98637e Binary files /dev/null and b/backend/lib/test/14 differ diff --git a/backend/lib/test/15 b/backend/lib/test/15 new file mode 100644 index 0000000..57c8e1b Binary files /dev/null and b/backend/lib/test/15 differ diff --git a/backend/lib/test/16 b/backend/lib/test/16 new file mode 100644 index 0000000..6f58828 Binary files /dev/null and b/backend/lib/test/16 differ diff --git a/backend/lib/test/17 b/backend/lib/test/17 new file mode 100644 index 0000000..95da2e0 Binary files /dev/null and b/backend/lib/test/17 differ diff --git a/backend/lib/test/18 b/backend/lib/test/18 new file mode 100644 index 0000000..a413d39 Binary files /dev/null and b/backend/lib/test/18 differ diff --git a/backend/lib/test/19 b/backend/lib/test/19 new file mode 100644 index 0000000..2c9a754 Binary files /dev/null and b/backend/lib/test/19 differ diff --git a/backend/lib/test/2 b/backend/lib/test/2 new file mode 100644 index 0000000..19aa7b4 Binary files /dev/null and b/backend/lib/test/2 differ diff --git a/backend/lib/test/20 b/backend/lib/test/20 new file mode 100644 index 0000000..61f3bb2 Binary files /dev/null and b/backend/lib/test/20 differ diff --git a/backend/lib/test/21 b/backend/lib/test/21 new file mode 100644 index 0000000..82c4fa2 Binary files /dev/null and b/backend/lib/test/21 differ diff --git a/backend/lib/test/22 b/backend/lib/test/22 new file mode 100644 index 0000000..46ec668 Binary files /dev/null and b/backend/lib/test/22 differ diff --git a/backend/lib/test/23 b/backend/lib/test/23 new file mode 100644 index 0000000..e495c71 Binary files /dev/null and b/backend/lib/test/23 differ diff --git a/backend/lib/test/24 b/backend/lib/test/24 new file mode 100644 index 0000000..2d73c33 Binary files /dev/null and b/backend/lib/test/24 differ diff --git a/backend/lib/test/25 b/backend/lib/test/25 new file mode 100644 index 0000000..ef1a35a Binary files /dev/null and b/backend/lib/test/25 differ diff --git a/backend/lib/test/26 b/backend/lib/test/26 new file mode 100644 index 0000000..1e1b452 Binary files /dev/null and b/backend/lib/test/26 differ diff --git a/backend/lib/test/27 b/backend/lib/test/27 new file mode 100644 index 0000000..76ce5be Binary files /dev/null and b/backend/lib/test/27 differ diff --git a/backend/lib/test/28 b/backend/lib/test/28 new file mode 100644 index 0000000..21e3ce5 Binary files /dev/null and b/backend/lib/test/28 differ diff --git a/backend/lib/test/29 b/backend/lib/test/29 new file mode 100644 index 0000000..8c07f34 Binary files /dev/null and b/backend/lib/test/29 differ diff --git a/backend/lib/test/3 b/backend/lib/test/3 new file mode 100644 index 0000000..a00e35d Binary files /dev/null and b/backend/lib/test/3 differ diff --git a/backend/lib/test/30 b/backend/lib/test/30 new file mode 100644 index 0000000..fffbe4e Binary files /dev/null and b/backend/lib/test/30 differ diff --git a/backend/lib/test/31 b/backend/lib/test/31 new file mode 100644 index 0000000..3bfa00f Binary files /dev/null and b/backend/lib/test/31 differ diff --git a/backend/lib/test/32 b/backend/lib/test/32 new file mode 100644 index 0000000..1801779 Binary files /dev/null and b/backend/lib/test/32 differ diff --git a/backend/lib/test/33 b/backend/lib/test/33 new file mode 100644 index 0000000..45482c6 Binary files /dev/null and b/backend/lib/test/33 differ diff --git a/backend/lib/test/34 b/backend/lib/test/34 new file mode 100644 index 0000000..7d45154 Binary files /dev/null and b/backend/lib/test/34 differ diff --git a/backend/lib/test/35 b/backend/lib/test/35 new file mode 100644 index 0000000..eeb5343 Binary files /dev/null and b/backend/lib/test/35 differ diff --git a/backend/lib/test/36 b/backend/lib/test/36 new file mode 100644 index 0000000..95d0e44 Binary files /dev/null and b/backend/lib/test/36 differ diff --git a/backend/lib/test/37 b/backend/lib/test/37 new file mode 100644 index 0000000..377889f Binary files /dev/null and b/backend/lib/test/37 differ diff --git a/backend/lib/test/38 b/backend/lib/test/38 new file mode 100644 index 0000000..a3232df Binary files /dev/null and b/backend/lib/test/38 differ diff --git a/backend/lib/test/39 b/backend/lib/test/39 new file mode 100644 index 0000000..42a3e41 Binary files /dev/null and b/backend/lib/test/39 differ diff --git a/backend/lib/test/4 b/backend/lib/test/4 new file mode 100644 index 0000000..34c6ff2 Binary files /dev/null and b/backend/lib/test/4 differ diff --git a/backend/lib/test/40 b/backend/lib/test/40 new file mode 100644 index 0000000..bb97fe1 Binary files /dev/null and b/backend/lib/test/40 differ diff --git a/backend/lib/test/41 b/backend/lib/test/41 new file mode 100644 index 0000000..2f8c60a Binary files /dev/null and b/backend/lib/test/41 differ diff --git a/backend/lib/test/42 b/backend/lib/test/42 new file mode 100644 index 0000000..bbea2d9 Binary files /dev/null and b/backend/lib/test/42 differ diff --git a/backend/lib/test/43 b/backend/lib/test/43 new file mode 100644 index 0000000..50bb497 Binary files /dev/null and b/backend/lib/test/43 differ diff --git a/backend/lib/test/44 b/backend/lib/test/44 new file mode 100644 index 0000000..233338e Binary files /dev/null and b/backend/lib/test/44 differ diff --git a/backend/lib/test/45 b/backend/lib/test/45 new file mode 100644 index 0000000..e8e62c0 Binary files /dev/null and b/backend/lib/test/45 differ diff --git a/backend/lib/test/46 b/backend/lib/test/46 new file mode 100644 index 0000000..e96bde4 Binary files /dev/null and b/backend/lib/test/46 differ diff --git a/backend/lib/test/47 b/backend/lib/test/47 new file mode 100644 index 0000000..5c67f4a Binary files /dev/null and b/backend/lib/test/47 differ diff --git a/backend/lib/test/48 b/backend/lib/test/48 new file mode 100644 index 0000000..3f8b42d Binary files /dev/null and b/backend/lib/test/48 differ diff --git a/backend/lib/test/49 b/backend/lib/test/49 new file mode 100644 index 0000000..4343c1b Binary files /dev/null and b/backend/lib/test/49 differ diff --git a/backend/lib/test/5 b/backend/lib/test/5 new file mode 100644 index 0000000..ffa194e Binary files /dev/null and b/backend/lib/test/5 differ diff --git a/backend/lib/test/50 b/backend/lib/test/50 new file mode 100644 index 0000000..6853003 Binary files /dev/null and b/backend/lib/test/50 differ diff --git a/backend/lib/test/51 b/backend/lib/test/51 new file mode 100644 index 0000000..4ad9dce Binary files /dev/null and b/backend/lib/test/51 differ diff --git a/backend/lib/test/52 b/backend/lib/test/52 new file mode 100644 index 0000000..0d84cd3 Binary files /dev/null and b/backend/lib/test/52 differ diff --git a/backend/lib/test/53 b/backend/lib/test/53 new file mode 100644 index 0000000..412cd21 Binary files /dev/null and b/backend/lib/test/53 differ diff --git a/backend/lib/test/54 b/backend/lib/test/54 new file mode 100644 index 0000000..5e642ca Binary files /dev/null and b/backend/lib/test/54 differ diff --git a/backend/lib/test/55 b/backend/lib/test/55 new file mode 100644 index 0000000..e25a3e3 Binary files /dev/null and b/backend/lib/test/55 differ diff --git a/backend/lib/test/56 b/backend/lib/test/56 new file mode 100644 index 0000000..65cabe6 Binary files /dev/null and b/backend/lib/test/56 differ diff --git a/backend/lib/test/57 b/backend/lib/test/57 new file mode 100644 index 0000000..20ca56f Binary files /dev/null and b/backend/lib/test/57 differ diff --git a/backend/lib/test/58 b/backend/lib/test/58 new file mode 100644 index 0000000..b72fd36 Binary files /dev/null and b/backend/lib/test/58 differ diff --git a/backend/lib/test/59 b/backend/lib/test/59 new file mode 100644 index 0000000..e1abb2d Binary files /dev/null and b/backend/lib/test/59 differ diff --git a/backend/lib/test/6 b/backend/lib/test/6 new file mode 100644 index 0000000..98c5dd5 Binary files /dev/null and b/backend/lib/test/6 differ diff --git a/backend/lib/test/60 b/backend/lib/test/60 new file mode 100644 index 0000000..2995f00 Binary files /dev/null and b/backend/lib/test/60 differ diff --git a/backend/lib/test/61 b/backend/lib/test/61 new file mode 100644 index 0000000..187bce1 Binary files /dev/null and b/backend/lib/test/61 differ diff --git a/backend/lib/test/62 b/backend/lib/test/62 new file mode 100644 index 0000000..051a6a6 Binary files /dev/null and b/backend/lib/test/62 differ diff --git a/backend/lib/test/63 b/backend/lib/test/63 new file mode 100644 index 0000000..a6f3108 Binary files /dev/null and b/backend/lib/test/63 differ diff --git a/backend/lib/test/64 b/backend/lib/test/64 new file mode 100644 index 0000000..a1ead3c Binary files /dev/null and b/backend/lib/test/64 differ diff --git a/backend/lib/test/65 b/backend/lib/test/65 new file mode 100644 index 0000000..ef909be Binary files /dev/null and b/backend/lib/test/65 differ diff --git a/backend/lib/test/66 b/backend/lib/test/66 new file mode 100644 index 0000000..a782946 Binary files /dev/null and b/backend/lib/test/66 differ diff --git a/backend/lib/test/67 b/backend/lib/test/67 new file mode 100644 index 0000000..f9a85b5 Binary files /dev/null and b/backend/lib/test/67 differ diff --git a/backend/lib/test/68 b/backend/lib/test/68 new file mode 100644 index 0000000..dfc6464 Binary files /dev/null and b/backend/lib/test/68 differ diff --git a/backend/lib/test/69 b/backend/lib/test/69 new file mode 100644 index 0000000..c10c6cb Binary files /dev/null and b/backend/lib/test/69 differ diff --git a/backend/lib/test/7 b/backend/lib/test/7 new file mode 100644 index 0000000..0a58369 Binary files /dev/null and b/backend/lib/test/7 differ diff --git a/backend/lib/test/70 b/backend/lib/test/70 new file mode 100644 index 0000000..2a4d9ce Binary files /dev/null and b/backend/lib/test/70 differ diff --git a/backend/lib/test/71 b/backend/lib/test/71 new file mode 100644 index 0000000..096ba81 Binary files /dev/null and b/backend/lib/test/71 differ diff --git a/backend/lib/test/72 b/backend/lib/test/72 new file mode 100644 index 0000000..006263f Binary files /dev/null and b/backend/lib/test/72 differ diff --git a/backend/lib/test/73 b/backend/lib/test/73 new file mode 100644 index 0000000..35a866f Binary files /dev/null and b/backend/lib/test/73 differ diff --git a/backend/lib/test/74 b/backend/lib/test/74 new file mode 100644 index 0000000..adfcdd8 Binary files /dev/null and b/backend/lib/test/74 differ diff --git a/backend/lib/test/75 b/backend/lib/test/75 new file mode 100644 index 0000000..d3d84fa Binary files /dev/null and b/backend/lib/test/75 differ diff --git a/backend/lib/test/8 b/backend/lib/test/8 new file mode 100644 index 0000000..a3704ef Binary files /dev/null and b/backend/lib/test/8 differ diff --git a/backend/lib/test/9 b/backend/lib/test/9 new file mode 100644 index 0000000..869017d Binary files /dev/null and b/backend/lib/test/9 differ diff --git a/backend/lib/test/collection.anki2 b/backend/lib/test/collection.anki2 new file mode 100644 index 0000000..fdda81e Binary files /dev/null and b/backend/lib/test/collection.anki2 differ diff --git a/backend/lib/test/media b/backend/lib/test/media new file mode 100644 index 0000000..991207c --- /dev/null +++ b/backend/lib/test/media @@ -0,0 +1 @@ +{"66": "rec1486791173.mp3", "12": "paste-5909874999552.jpg", "49": "rec1486790484.mp3", "54": "rec1486789453.mp3", "59": "paste-6051608920308.jpg", "75": "paste-7176890351876.jpg", "50": "paste-5952824672506.jpg", "11": "paste-5342939316470.jpg", "3": "rec1486790515.mp3", "73": "rec1486791444.mp3", "58": "paste-6743098654966.jpg", "26": "rec1486791092.mp3", "60": "paste-5596342386925.jpg", "68": "rec1486787151.mp3", "69": "rec1486790743.mp3", "0": "rec1486791250.mp3", "8": "paste-6219112644848.jpg", "18": "rec1486790648.mp3", "57": "paste-6880537608452.jpg", "61": "rec1486789557.mp3", "33": "paste-6343666696438.jpg", "7": "rec1486791037.mp3", "21": "rec1486786900.mp3", "44": "paste-5476083302645.jpg", "17": "rec1486790321.mp3", "9": "rec1486790979.mp3", "55": "rec1486790707.mp3", "64": "rec1486790888.mp3", "71": "paste-7563437408511.jpg", "53": "paste-6532645257453.jpg", "36": "paste-6322191859961.jpg", "31": "paste-7687991460090.jpg", "28": "rec1486790193.mp3", "47": "rec1486791121.mp3", "39": "rec1486791213.mp3", "14": "paste-7022271529203.jpg", "37": "paste-7288559501563.jpg", "15": "rec1486789738.mp3", "51": "paste-2929167696123.jpg", "25": "paste-7597797146867.jpg", "20": "rec1486790550.mp3", "19": "rec1486790446.mp3", "48": "rec1486788047.mp3", "74": "paste-5849745457397.jpg", "30": "rec1486791007.mp3", "10": "rec1486788946.mp3", "29": "paste-6846177870075.jpg", "4": "rec1486790287.mp3", "56": "rec1486789680.mp3", "62": "paste-5755256176881.jpg", "38": "paste-2143188680961.jpg", "70": "rec1486790804.mp3", "42": "rec1486790858.mp3", "43": "paste-7430293422343.jpg", "23": "paste-6158983102706.jpg", "72": "rec1486789502.mp3", "34": "rec1486790589.mp3", "16": "rec1486791310.mp3", "52": "paste-6266357285108.jpg", "32": "paste-687194767612.jpg", "65": "paste-6708738916592.jpg", "2": "paste-7142530613495.jpg", "40": "paste-816043786497.jpg", "1": "paste-1090921693442.jpg", "67": "rec1486791391.mp3", "5": "rec1486790677.mp3", "6": "rec1486791362.mp3", "46": "rec1486790948.mp3", "22": "paste-7073811136753.jpg", "63": "rec1486790918.mp3", "24": "rec1486790620.mp3", "27": "paste-4939212390648.jpg", "45": "paste-6644314407145.jpg", "41": "paste-5811090751727.jpg", "35": "rec1486789528.mp3", "13": "paste-6442450944247.jpg"} \ No newline at end of file diff --git a/backend/lib/utils.js b/backend/lib/utils.js new file mode 100644 index 0000000..693da49 --- /dev/null +++ b/backend/lib/utils.js @@ -0,0 +1 @@ +export {} \ No newline at end of file diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..1b91a81 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,23 @@ +{ + "name": "backend", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "start": "node ./bin/www", + "reinstall": "rm -rf node_modules/ package-lock.json && npm install" + }, + "dependencies": { + "adm-zip": "^0.5.16", + "axios": "^1.13.5", + "cookie-parser": "~1.4.4", + "debug": "~2.6.9", + "express": "~4.16.1", + "fs": "^0.0.1-security", + "http-errors": "~1.6.3", + "jade": "~1.11.0", + "morgan": "~1.9.1", + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7" + } +} diff --git a/backend/routes/index.js b/backend/routes/index.js new file mode 100644 index 0000000..ecca96a --- /dev/null +++ b/backend/routes/index.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); + +/* GET home page. */ +router.get('/', function(req, res, next) { + res.render('index', { title: 'Express' }); +}); + +module.exports = router; diff --git a/backend/routes/photosearch.js b/backend/routes/photosearch.js new file mode 100644 index 0000000..0563ee4 --- /dev/null +++ b/backend/routes/photosearch.js @@ -0,0 +1,123 @@ +var express = require('express') +var router = express.Router() + +const axios = require('axios') + +/** + * Services to add: + * - https://unsplash.com/developers + * - https://pixabay.com/api/docs/ + * + * Pronounciation will require $2/mo sub: + * https://api.forvo.com/plans-and-pricing/ + */ + +async function pexelsSearch(query) { + // https://help.pexels.com/hc/en-us/articles/47678194141337-Can-I-change-the-search-language-when-using-the-Pexels-API + // pexels has a monthly rate limit and an hourly rate limit, only the monthly is returned in the headers + // limit is also 200 per hour, will need to manually track that + + const resp = await axios.get( + `https://api.pexels.com/v1/search?query=${encodeURIComponent(query)}&per_page=5`, + { + headers: { + Authorization: process.env.PEXELS_API, + }, + } + ) + + const { data, headers } = resp + const photos = data.photos + + let filtered = photos.map((p) => ( + { + url: p.src.medium, + desc: p.alt, + credit: p.photographer, + id: p.id + } + )) + + let response = { + photos: filtered, + ratelimit: { + limit: headers['x-ratelimit-limit'], + remaining: headers['x-ratelimit-remaining'], + reset: new Date(Number(headers['x-ratelimit-reset']) * 1000) + } + } + + return response; +} + +async function shutterstockSearch(query) { + + // shutterstock has an hourly rate limit and no monthly limit + // https://api-reference.shutterstock.com/?shell#assets-api + // higher quality is available with shutterstock watermarks, look into [photo].assets objects + + const SHUTTERSTOCK_API_TOKEN = process.env.SHUTTERSTOCK_API + + try { + const resp = await axios.get( + 'https://api.shutterstock.com/v2/images/search', + { + headers: { + 'Accept': 'application/json', + 'Authorization': `Bearer ${SHUTTERSTOCK_API_TOKEN}` + }, + params: { + query, + per_page: 5, + sort: 'popular', + safe: false, + image_type: 'photo' + } + } + ) + + let { data, headers } = resp + + let filtered = data.data.map((p) => ( + { + url: p.assets.mosaic.url, + desc: p.description, + credit: p.contributor.id, + id: Number(p.id) + } + )) + + let response = { + photos: filtered, + ratelimit: { + limit: headers['ratelimit-limit'], + remaining: headers['ratelimit-remaining'], + reset: new Date(Number(headers['ratelimit-reset'])) + } + } + + return response; + + } catch (error) { + console.error('Error:', error); + }`` +} + +router.get('/', async function(req, res) { + try { + + const queryL1 = req.query.queries[0] + // const queryL2 = req.query.queries[1] + + // const pexels = await pexelsSearch(queryL1) + const shutterstock = await shutterstockSearch(queryL1) + + response = shutterstock; + + res.status(200).send(response) + } catch (err) { + console.log(err) + } +}); + +module.exports = router diff --git a/backend/routes/users.js b/backend/routes/users.js new file mode 100644 index 0000000..1b861a1 --- /dev/null +++ b/backend/routes/users.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +router.get('/', function(req, res, next) { + res.status(200).send('respond with a resource'); +}); + +module.exports = router; diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 0000000..3b510aa --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,8 @@ +[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}] +charset = utf-8 +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +max_line_length = 100 diff --git a/frontend/.oxlintrc.json b/frontend/.oxlintrc.json new file mode 100644 index 0000000..1c0980f --- /dev/null +++ b/frontend/.oxlintrc.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["eslint", "unicorn", "oxc", "vue"], + "env": { + "browser": true + }, + "categories": { + "correctness": "error" + } +} diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json new file mode 100644 index 0000000..29a2402 --- /dev/null +++ b/frontend/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "singleQuote": true, + "printWidth": 100 +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..3370856 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,44 @@ +# . + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Recommended Browser Setup + +- Chromium-based browsers (Chrome, Edge, Brave, etc.): + - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) + - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) +- Firefox: + - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) + - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Compile and Minify for Production + +```sh +npm run build +``` + +### Lint with [ESLint](https://eslint.org/) + +```sh +npm run lint +``` diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..a739435 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,30 @@ +import { defineConfig, globalIgnores } from 'eslint/config' +import globals from 'globals' +import js from '@eslint/js' +import pluginVue from 'eslint-plugin-vue' +import pluginOxlint from 'eslint-plugin-oxlint' +import skipFormatting from 'eslint-config-prettier/flat' + +export default defineConfig([ + { + name: 'app/files-to-lint', + files: ['**/*.{vue,js,mjs,jsx}'], + }, + + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + + { + languageOptions: { + globals: { + ...globals.browser, + }, + }, + }, + + js.configs.recommended, + ...pluginVue.configs['flat/essential'], + + ...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'), + + skipFormatting, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..b19040a --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json new file mode 100644 index 0000000..5a1f2d2 --- /dev/null +++ b/frontend/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..560c425 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,43 @@ +{ + "name": "linguamate", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "reinstall": "rm -rf node_modules/ package-lock.json && npm install", + "lint": "run-s lint:*", + "lint:oxlint": "oxlint . --fix", + "lint:eslint": "eslint . --fix --cache", + "format": "prettier --write --experimental-cli src/" + }, + "dependencies": { + "@mdi/font": "^7.4.47", + "axios": "^1.13.5", + "pinia": "^3.0.4", + "primeflex": "^4.0.0", + "vue": "^3.5.27", + "vue-router": "^5.0.1" + }, + "devDependencies": { + "@eslint/js": "^9.39.2", + "@vitejs/plugin-vue": "^6.0.3", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-oxlint": "~1.42.0", + "eslint-plugin-vue": "~10.7.0", + "globals": "^17.3.0", + "npm-run-all2": "^8.0.4", + "oxlint": "~1.42.0", + "prettier": "3.8.1", + "sass-embedded": "^1.97.3", + "unplugin-vue-components": "^31.0.0", + "vite": "^7.3.1", + "vite-plugin-vue-devtools": "^8.0.5" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..4b7399f --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,9 @@ + + + + diff --git a/frontend/src/components/app/TheMenu.vue b/frontend/src/components/app/TheMenu.vue new file mode 100644 index 0000000..4094651 --- /dev/null +++ b/frontend/src/components/app/TheMenu.vue @@ -0,0 +1,62 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/decks/CardEdit.vue b/frontend/src/components/decks/CardEdit.vue new file mode 100644 index 0000000..970c3f9 --- /dev/null +++ b/frontend/src/components/decks/CardEdit.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/frontend/src/components/decks/PhotoSearch.vue b/frontend/src/components/decks/PhotoSearch.vue new file mode 100644 index 0000000..451b1a9 --- /dev/null +++ b/frontend/src/components/decks/PhotoSearch.vue @@ -0,0 +1,54 @@ + + + diff --git a/frontend/src/js/api.js b/frontend/src/js/api.js new file mode 100644 index 0000000..eb9f4a6 --- /dev/null +++ b/frontend/src/js/api.js @@ -0,0 +1,20 @@ +import axios from 'axios' +const client = axios.create({ + baseURL: 'http://localhost:3000', + json: true +}) + +async function photosearch(...queries) { + let resp = await axios.get('http://localhost:3000/search/photos/', { + params: { queries }, + paramsSerializer: { + indexes: true, // use brackets with indexes + } + }) + console.log(resp.data.ratelimit) + return resp.data +} + +export default { + photosearch +} diff --git a/frontend/src/js/utils.js b/frontend/src/js/utils.js new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..95ebf50 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,29 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' + +import App from './App.vue' +import router from './router' +import PrimeVue from 'primevue/config'; + +// style +import Aura from '@primeuix/themes/aura'; +import './style.scss'; +import 'primeflex/primeflex.css' + +const app = createApp(App) + +app.use(createPinia()) +app.use(router) +app.use(PrimeVue, { + // https://primevue.org/theming/styled/ + theme: { + preset: Aura, + options: { + prefix: 'p', + // darkModeSelector: 'system', + cssLayer: false + } + } + }) + +app.mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..649f77f --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,21 @@ +import { createRouter, createWebHistory } from 'vue-router' + +import Home from '../views/Home.vue' +import Decks from '../views/Decks.vue' +import Pronunciation from '../views/Pronunciation.vue' +import Test from '../views/Test.vue' +import Toolkit from '../views/Toolkit.vue' + +const router = createRouter({ + // https://router.vuejs.org/guide/ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { path: '/', component: Home }, + { path: '/toolkit', component: Toolkit }, + { path: '/speak', component: Pronunciation }, + { path: '/decks', component: Decks }, + { path: '/test', component: Test }, + ], +}) + +export default router \ No newline at end of file diff --git a/frontend/src/stores/counter.js b/frontend/src/stores/counter.js new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/frontend/src/stores/counter.js @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/frontend/src/style.scss b/frontend/src/style.scss new file mode 100644 index 0000000..94fa80f --- /dev/null +++ b/frontend/src/style.scss @@ -0,0 +1,5 @@ +@import '@mdi/font/css/materialdesignicons.min.css'; + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; +} diff --git a/frontend/src/views/Decks.vue b/frontend/src/views/Decks.vue new file mode 100644 index 0000000..bb7bcec --- /dev/null +++ b/frontend/src/views/Decks.vue @@ -0,0 +1,12 @@ + + + diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue new file mode 100644 index 0000000..8cffda1 --- /dev/null +++ b/frontend/src/views/Home.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/frontend/src/views/Pronunciation.vue b/frontend/src/views/Pronunciation.vue new file mode 100644 index 0000000..8411247 --- /dev/null +++ b/frontend/src/views/Pronunciation.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/frontend/src/views/Test.vue b/frontend/src/views/Test.vue new file mode 100644 index 0000000..656bcab --- /dev/null +++ b/frontend/src/views/Test.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/frontend/src/views/Toolkit.vue b/frontend/src/views/Toolkit.vue new file mode 100644 index 0000000..efbb942 --- /dev/null +++ b/frontend/src/views/Toolkit.vue @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..0fcf68e --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,22 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +import Components from 'unplugin-vue-components/vite'; +import { PrimeVueResolver } from 'unplugin-vue-components/resolvers'; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + Components({ resolvers: [PrimeVueResolver()], }), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, +})