Skip to content

Commit 151e6ad

Browse files
authored
Merge pull request #110 from NeuroJSON/staging
Backend Framework Setup: Database Migrations, Models, and Dataset Interaction Endpoints
2 parents 1bee111 + 4149012 commit 151e6ad

37 files changed

+6649
-183
lines changed

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ node_modules
33
.DS_Store
44
package-lock.json
55

6-
build
6+
build
7+
8+
#backend
9+
backend/node_modules/
10+
backend/.env
11+
backend/*.sqlite
12+
!backend/package-lock.json

backend/.env.example

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Server Configuration
2+
PORT=5000
3+
NODE_ENV=development
4+
5+
# Database Configuration
6+
# For local development (SQLite)
7+
DB_DIALECT=sqlite
8+
DB_STORAGE=./database.sqlite
9+
10+
# For production (PostgreSQL) - uncomment and fill these
11+
# DB_DIALECT=postgres
12+
# DB_HOST=localhost
13+
# DB_PORT=5432
14+
# DB_NAME=neurojson_db
15+
# DB_USER=your_username
16+
# DB_PASSWORD=your_password
17+
18+
# JWT Secret
19+
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
20+
21+
# CORS Origin (your React frontend URL)
22+
CORS_ORIGIN=http://localhost:3000
23+

backend/.gitignore

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Environment variables
2+
.env
3+
.env.local
4+
.env.*.local
5+
!.env.example
6+
7+
# Database files (SQLite)
8+
*.sqlite
9+
*.sqlite3
10+
*.db
11+
12+
# Dependencies
13+
node_modules/
14+
npm-debug.log*
15+
yarn-debug.log*
16+
yarn-error.log*
17+
18+
# Logs
19+
logs/
20+
*.log
21+
22+
# OS files
23+
.DS_Store
24+
Thumbs.db
25+
26+
# Build output
27+
dist/
28+
build/

backend/.sequelizerc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// backend/.sequelizerc
2+
const path = require("path");
3+
4+
module.exports = {
5+
config: path.resolve("config", "config.js"),
6+
"models-path": path.resolve("src", "models"),
7+
"seeders-path": path.resolve("seeders"),
8+
"migrations-path": path.resolve("migrations"),
9+
};

backend/config/config.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
require("dotenv").config();
2+
3+
module.exports = {
4+
development: {
5+
dialect: "sqlite",
6+
storage: "./database.sqlite",
7+
logging: console.log,
8+
},
9+
test: {
10+
dialect: "sqlite",
11+
storage: ":memory:",
12+
logging: false,
13+
},
14+
production: {
15+
dialect: "postgres",
16+
host: process.env.DB_HOST,
17+
port: parseInt(process.env.DB_PORT || "5432"),
18+
database: process.env.DB_NAME,
19+
username: process.env.DB_USER,
20+
password: process.env.DB_PASSWORD,
21+
logging: false,
22+
pool: {
23+
max: 5,
24+
min: 0,
25+
acquire: 30000,
26+
idle: 10000,
27+
},
28+
},
29+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"use strict";
2+
3+
/** @type {import('sequelize-cli').Migration} */
4+
module.exports = {
5+
async up(queryInterface, Sequelize) {
6+
/**
7+
* Add altering commands here.
8+
*
9+
* Example:
10+
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
11+
*/
12+
await queryInterface.createTable("users", {
13+
id: {
14+
type: Sequelize.INTEGER,
15+
autoIncrement: true,
16+
primaryKey: true,
17+
allowNull: false,
18+
},
19+
orcid_id: {
20+
type: Sequelize.STRING(255),
21+
allowNull: true,
22+
unique: true,
23+
},
24+
google_id: {
25+
type: Sequelize.STRING(255),
26+
allowNull: true,
27+
unique: true,
28+
},
29+
github_id: {
30+
type: Sequelize.STRING(255),
31+
allowNull: true,
32+
unique: true,
33+
},
34+
username: {
35+
type: Sequelize.STRING(255),
36+
allowNull: false,
37+
unique: true,
38+
},
39+
hashed_password: {
40+
type: Sequelize.STRING(255),
41+
allowNull: true,
42+
},
43+
email: {
44+
type: Sequelize.STRING(255),
45+
allowNull: false,
46+
unique: true,
47+
},
48+
created_at: {
49+
type: Sequelize.DATE,
50+
allowNull: false,
51+
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"),
52+
},
53+
updated_at: {
54+
type: Sequelize.DATE,
55+
allowNull: false,
56+
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"),
57+
},
58+
});
59+
},
60+
61+
async down(queryInterface, Sequelize) {
62+
/**
63+
* Add reverting commands here.
64+
*
65+
* Example:
66+
* await queryInterface.dropTable('users');
67+
*/
68+
await queryInterface.dropTable("users");
69+
},
70+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use strict";
2+
3+
/** @type {import('sequelize-cli').Migration} */
4+
module.exports = {
5+
async up(queryInterface, Sequelize) {
6+
await queryInterface.createTable("datasets", {
7+
id: {
8+
type: Sequelize.INTEGER,
9+
autoIncrement: true,
10+
primaryKey: true,
11+
allowNull: false,
12+
},
13+
couch_db: {
14+
type: Sequelize.STRING(255),
15+
allowNull: false,
16+
},
17+
ds_id: {
18+
type: Sequelize.STRING(255),
19+
allowNull: false,
20+
},
21+
views_count: {
22+
type: Sequelize.INTEGER,
23+
allowNull: true,
24+
defaultValue: 0,
25+
},
26+
});
27+
// Add unique constraint on couch_db + ds_id
28+
await queryInterface.addIndex("datasets", ["couch_db", "ds_id"], {
29+
unique: true,
30+
name: "datasets_couch_db_ds_id_unique",
31+
});
32+
},
33+
34+
async down(queryInterface, Sequelize) {
35+
await queryInterface.dropTable("datasets");
36+
},
37+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use strict";
2+
3+
/** @type {import('sequelize-cli').Migration} */
4+
module.exports = {
5+
async up(queryInterface, Sequelize) {
6+
await queryInterface.createTable("dataset_likes", {
7+
id: {
8+
type: Sequelize.INTEGER,
9+
autoIncrement: true,
10+
primaryKey: true,
11+
allowNull: false,
12+
},
13+
user_id: {
14+
type: Sequelize.INTEGER,
15+
allowNull: false,
16+
references: {
17+
model: "users",
18+
key: "id",
19+
},
20+
onUpdate: "CASCADE",
21+
onDelete: "CASCADE",
22+
},
23+
dataset_id: {
24+
type: Sequelize.INTEGER,
25+
allowNull: false,
26+
references: {
27+
model: "datasets",
28+
key: "id",
29+
},
30+
onUpdate: "CASCADE",
31+
onDelete: "CASCADE",
32+
},
33+
created_at: {
34+
type: Sequelize.DATE,
35+
allowNull: false,
36+
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"),
37+
},
38+
});
39+
40+
// Add unique constraint to prevent duplicate likes
41+
await queryInterface.addIndex("dataset_likes", ["user_id", "dataset_id"], {
42+
unique: true,
43+
name: "dataset_likes_user_dataset_unique",
44+
});
45+
},
46+
47+
async down(queryInterface, Sequelize) {
48+
await queryInterface.dropTable("dataset_likes");
49+
},
50+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use strict";
2+
3+
/** @type {import('sequelize-cli').Migration} */
4+
module.exports = {
5+
async up(queryInterface, Sequelize) {
6+
await queryInterface.createTable("saved_datasets", {
7+
id: {
8+
type: Sequelize.INTEGER,
9+
autoIncrement: true,
10+
primaryKey: true,
11+
allowNull: false,
12+
},
13+
user_id: {
14+
type: Sequelize.INTEGER,
15+
allowNull: false,
16+
references: {
17+
model: "users",
18+
key: "id",
19+
},
20+
onUpdate: "CASCADE",
21+
onDelete: "CASCADE",
22+
},
23+
dataset_id: {
24+
type: Sequelize.INTEGER,
25+
allowNull: false,
26+
references: {
27+
model: "datasets",
28+
key: "id",
29+
},
30+
onUpdate: "CASCADE",
31+
onDelete: "CASCADE",
32+
},
33+
created_at: {
34+
type: Sequelize.DATE,
35+
allowNull: false,
36+
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"),
37+
},
38+
});
39+
40+
// Add unique constraint to prevent duplicate saves
41+
await queryInterface.addIndex("saved_datasets", ["user_id", "dataset_id"], {
42+
unique: true,
43+
name: "saved_datasets_user_dataset_unique",
44+
});
45+
},
46+
47+
async down(queryInterface, Sequelize) {
48+
await queryInterface.dropTable("saved_datasets");
49+
},
50+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"use strict";
2+
3+
/** @type {import('sequelize-cli').Migration} */
4+
module.exports = {
5+
async up(queryInterface, Sequelize) {
6+
await queryInterface.createTable("comments", {
7+
id: {
8+
type: Sequelize.INTEGER,
9+
autoIncrement: true,
10+
primaryKey: true,
11+
allowNull: false,
12+
},
13+
user_id: {
14+
type: Sequelize.INTEGER,
15+
allowNull: false,
16+
references: {
17+
model: "users",
18+
key: "id",
19+
},
20+
onUpdate: "CASCADE",
21+
onDelete: "CASCADE",
22+
},
23+
dataset_id: {
24+
type: Sequelize.INTEGER,
25+
allowNull: false,
26+
references: {
27+
model: "datasets",
28+
key: "id",
29+
},
30+
onUpdate: "CASCADE",
31+
onDelete: "CASCADE",
32+
},
33+
body: {
34+
type: Sequelize.TEXT,
35+
allowNull: false,
36+
},
37+
created_at: {
38+
type: Sequelize.DATE,
39+
allowNull: false,
40+
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"),
41+
},
42+
updated_at: {
43+
type: Sequelize.DATE,
44+
allowNull: false,
45+
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"),
46+
},
47+
});
48+
49+
// Add indexes for common queries
50+
await queryInterface.addIndex("comments", ["dataset_id"]);
51+
await queryInterface.addIndex("comments", ["user_id"]);
52+
},
53+
54+
async down(queryInterface, Sequelize) {
55+
await queryInterface.dropTable("comments");
56+
},
57+
};

0 commit comments

Comments
 (0)