Skip to content

Commit f85ac7c

Browse files
First commit for open-source Ġabra API
1 parent 9131223 commit f85ac7c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+14846
-0
lines changed

.gitignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Server config is private
2+
server-config.js
3+
4+
# Data dumps
5+
public/data/*
6+
7+
# Logs
8+
logs
9+
*.log
10+
11+
# Runtime data
12+
pids
13+
*.pid
14+
*.seed
15+
16+
# Directory for instrumented libs generated by jscoverage/JSCover
17+
lib-cov
18+
19+
# Coverage directory used by tools like istanbul
20+
coverage
21+
22+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23+
.grunt
24+
25+
# node-waf configuration
26+
.lock-wscript
27+
28+
# Compiled binary addons (http://nodejs.org/api/addons.html)
29+
build/Release
30+
31+
# Dependency directory
32+
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
33+
node_modules

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
API for Ġabra
2+
-------------
3+
4+
Ġabra is an open lexicon for Maltese.
5+
6+
## Installation
7+
8+
### Web app
9+
10+
- You need NodeJS. After cloning the repo run `npm install` to install Node packages locally.
11+
12+
- You will need a file `server-config.js` with relevant details for your host.
13+
Start by copying `server-config.sample.js`.
14+
15+
### Database
16+
17+
- You will need a MongoDB installation.
18+
See <http://mlrs.research.um.edu.mt/resources/gabra-api/download> for data dumps you can use to get started.
19+
TODO: non-data tables
20+
21+
## Running
22+
23+
Use PM2: `pm2 start processes.json` or just run the file `start.sh`.

app.js

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
var express = require('express')
2+
var path = require('path')
3+
var logger = require('morgan')
4+
var cookieParser = require('cookie-parser')
5+
var bodyParser = require('body-parser')
6+
var compression = require('compression')
7+
8+
var app = express()
9+
10+
app.use(compression()) // compress all requests
11+
12+
// view engine setup
13+
app.set('views', path.join(__dirname, 'views'))
14+
app.set('view engine', 'jade')
15+
app.locals.pretty = true
16+
17+
app.use(logger('dev'))
18+
app.use(bodyParser.json())
19+
app.use(bodyParser.urlencoded({ extended: false }))
20+
app.use(cookieParser())
21+
var cache_static = '7 days'
22+
app.use(express.static(path.join(__dirname, 'public'), {maxAge: cache_static}))
23+
24+
// CORS
25+
app.use(function (req, res, next) {
26+
// var allowed = [
27+
// 'http://localhost',
28+
// 'http://mlrs.research.um.edu.mt'
29+
// ]
30+
// var origin = req.headers.origin
31+
// if (allowed.indexOf(origin) !== -1) {
32+
// res.header('Access-Control-Allow-Origin', origin)
33+
// }
34+
res.header('Access-Control-Allow-Origin', '*')
35+
next()
36+
})
37+
38+
// Load server-specific config
39+
var config = require('./server-config')
40+
app.use(function (req, res, next) {
41+
res.locals = config
42+
next()
43+
})
44+
45+
// Stop if maintenance mode
46+
app.use(function (req, res, next) {
47+
if (res.locals.maintenanceMode) {
48+
res.status('503')
49+
res.header('Retry-After', 120) // two minutes
50+
res.send('Down for maintenance')
51+
res.end()
52+
} else {
53+
next()
54+
}
55+
})
56+
57+
// Database
58+
var monk = require('monkii')
59+
var db = monk(config.dbUrl, config.dbOptions)
60+
// Make our db accessible to our router
61+
app.use(function (req, res, next) {
62+
req.db = db
63+
next()
64+
})
65+
66+
// Authentication
67+
var passport = require('passport')
68+
var BasicStrategy = require('passport-http').BasicStrategy
69+
passport.use(new BasicStrategy(
70+
function (username, password, done) {
71+
db.get('users').findOne({username: username}, function (err, user) {
72+
var salted = config.salt + password
73+
var shasum = require('crypto').createHash('sha1')
74+
var hashed = shasum.update(salted).digest('hex')
75+
if (err) { return done(err) }
76+
if (!user) { return done(null, false, {message: 'Unknown user.'}) }
77+
if (user.password !== hashed) { return done(null, false, {message: 'Incorrect password.'}) }
78+
return done(null, user)
79+
})
80+
}
81+
))
82+
app.use(passport.initialize())
83+
84+
// Analytics
85+
if (config.analyticsCode) {
86+
app.set('trust proxy', 'loopback')
87+
var ua = require('universal-analytics')
88+
var visitor = ua(config.analyticsCode)
89+
var pageview = function (req, res, next) {
90+
var params = {
91+
'dp': req.originalUrl,
92+
'uip': req.ip
93+
}
94+
visitor.pageview(params, function (err) {
95+
if (err) {
96+
console.log(err)
97+
}
98+
})
99+
next()
100+
}
101+
if (!config.developmentMode) {
102+
app.use('/lexemes', pageview)
103+
app.use('/wordforms', pageview)
104+
app.use('/roots', pageview)
105+
}
106+
}
107+
108+
// Routing
109+
app.use('/', require('./routes/index'))
110+
app.use('/lexemes', require('./routes/lexemes'))
111+
app.use('/wordforms', require('./routes/wordforms'))
112+
app.use('/roots', require('./routes/roots'))
113+
app.use('/sources', require('./routes/sources'))
114+
app.use('/feedback', require('./routes/feedback'))
115+
app.use('/logs', require('./routes/logs'))
116+
app.use('/i18n', require('./routes/i18n'))
117+
app.use('/morpho', require('./routes/morpho'))
118+
119+
// http://stackoverflow.com/a/27464258/98600
120+
// app.use('/json-editor', express.static(__dirname + '/node_modules/json-editor/dist/'))
121+
// app.use('/bootstrap', express.static(__dirname + '/node_modules/bootstrap/dist/'))
122+
// app.use('/ladda', express.static(__dirname + '/node_modules/ladda/dist/'))
123+
app.use('/module', express.static(path.join(__dirname, '/node_modules/'), {maxAge: cache_static}))
124+
125+
// catch 404 and forward to error handler
126+
app.use(function (req, res, next) {
127+
var err = new Error('Not Found')
128+
err.status = 404
129+
next(err)
130+
})
131+
132+
// error handlers
133+
134+
// development error handler
135+
// will print stacktrace
136+
if (app.get('env') === 'development') {
137+
app.use(function (err, req, res, next) {
138+
res.status(err.status || 500)
139+
res.render('error', {
140+
message: err.message,
141+
error: err
142+
})
143+
})
144+
}
145+
146+
// production error handler
147+
// no stacktraces leaked to user
148+
app.use(function (err, req, res, next) {
149+
res.status(err.status || 500)
150+
res.render('error', {
151+
message: err.message,
152+
error: {}
153+
})
154+
})
155+
156+
module.exports = app

bin/www

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env node
2+
3+
// process.env.DEBUG = 'monk:queries'
4+
5+
/**
6+
* Module dependencies.
7+
*/
8+
9+
var app = require('../app');
10+
var debug = require('debug')('api:server');
11+
var http = require('http');
12+
13+
/**
14+
* Get port from environment and store in Express.
15+
*/
16+
17+
var port = normalizePort(process.env.PORT || '3000');
18+
app.set('port', port);
19+
20+
/**
21+
* Create HTTP server.
22+
*/
23+
24+
var server = http.createServer(app);
25+
26+
/**
27+
* Listen on provided port, on all network interfaces.
28+
*/
29+
30+
server.listen(port);
31+
server.on('error', onError);
32+
server.on('listening', onListening);
33+
34+
/**
35+
* Normalize a port into a number, string, or false.
36+
*/
37+
38+
function normalizePort(val) {
39+
var port = parseInt(val, 10);
40+
41+
if (isNaN(port)) {
42+
// named pipe
43+
return val;
44+
}
45+
46+
if (port >= 0) {
47+
// port number
48+
return port;
49+
}
50+
51+
return false;
52+
}
53+
54+
/**
55+
* Event listener for HTTP server "error" event.
56+
*/
57+
58+
function onError(error) {
59+
if (error.syscall !== 'listen') {
60+
throw error;
61+
}
62+
63+
var bind = typeof port === 'string'
64+
? 'Pipe ' + port
65+
: 'Port ' + port;
66+
67+
// handle specific listen errors with friendly messages
68+
switch (error.code) {
69+
case 'EACCES':
70+
console.error(bind + ' requires elevated privileges');
71+
process.exit(1);
72+
break;
73+
case 'EADDRINUSE':
74+
console.error(bind + ' is already in use');
75+
process.exit(1);
76+
break;
77+
default:
78+
throw error;
79+
}
80+
}
81+
82+
/**
83+
* Event listener for HTTP server "listening" event.
84+
*/
85+
86+
function onListening() {
87+
var addr = server.address();
88+
var bind = typeof addr === 'string'
89+
? 'pipe ' + addr
90+
: 'port ' + addr.port;
91+
debug('Listening on ' + bind);
92+
}

logger.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// for logging changes to Gabra
2+
module.exports = {
3+
4+
// Make a logger: a function which asynchronously adds update to log
5+
// ObjectID can be either object or string
6+
makeLogger: function (collection_name) {
7+
return function (req, object_id, new_val) {
8+
var coll = req.db.get('logs')
9+
var user = req.user ? req.user.username : ''
10+
if (typeof object_id !== 'object') {
11+
object_id = coll.id(object_id)
12+
}
13+
var obj = {
14+
'collection': collection_name,
15+
'object_id': object_id,
16+
'date': new Date(),
17+
'new_value': new_val,
18+
'username': user
19+
}
20+
coll.insert(obj)
21+
}
22+
}
23+
24+
}

morpho/adjective.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
var extend = require('extend')
2+
module.exports = {
3+
4+
inflect: function (body, callback) {
5+
var lemma = body.lemma // 'bravu'
6+
if (!lemma) {
7+
return callback('No lemma provided', null)
8+
}
9+
10+
var infs = inflections(lemma)
11+
var forms = [
12+
{
13+
'surface_form': infs.m,
14+
'number': 'sg',
15+
'gender': 'm'
16+
},
17+
{
18+
'surface_form': infs.f,
19+
'number': 'sg',
20+
'gender': 'f'
21+
},
22+
{
23+
'surface_form': infs.pl,
24+
'number': 'pl',
25+
'gender': 'mf'
26+
}
27+
]
28+
var extras = {
29+
'sources': [sourceKey]
30+
}
31+
for (var f in forms) {
32+
extend(true, forms[f], extras)
33+
}
34+
35+
callback(null, forms)
36+
}
37+
}
38+
39+
const sourceKey = 'Camilleri2015'
40+
41+
function inflections (lemma) {
42+
var m = lemma
43+
var f
44+
var pl
45+
if (lemma.match(/u$/)) { // bravu
46+
var stem = lemma.slice(0, -1)
47+
f = stem + 'a' // brava
48+
pl = stem + 'i' // bravi
49+
} else if (lemma.match(/i$/)) { // mimli
50+
f = lemma + 'ja' // mimlija
51+
pl = lemma + 'jin' // mimlijin
52+
} else {
53+
f = lemma + 'a' // maħmuġa
54+
pl = lemma + 'in' // maħmuġin
55+
}
56+
return {
57+
m: m,
58+
f: f,
59+
pl: pl
60+
}
61+
}

0 commit comments

Comments
 (0)