Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1b5c560
fix: Updated docker-compose files for production and development envi…
ananyaa06 Jun 29, 2025
fea431a
feat(api): add secure site management and public sites endpoints
ananyaa06 Jul 12, 2025
6d7ebd9
feat(core): add mongoose schema for Site model
ananyaa06 Jul 12, 2025
8230e28
feat: add secure site management endpoints
ananyaa06 Jul 16, 2025
0992445
fix(routes): add leading slash to secure-site endpoints
ananyaa06 Jul 16, 2025
1c217d4
feat: add public sites API endpoint
ananyaa06 Jul 16, 2025
6e35be7
refactor: address PR review comments
ananyaa06 Jul 17, 2025
0af04c9
fix: address PR review comments
ananyaa06 Jul 17, 2025
5ce36c1
refactor: change sites to public-sites to not confuse with old endpoi…
ananyaa06 Aug 1, 2025
56f13c2
feat: add validation for latitude and longitude
ananyaa06 Aug 7, 2025
500adf2
docs: add comments to secure-site route handlers
ananyaa06 Aug 7, 2025
2f27281
refactor: replace api/public-sites with api/sites and make necessary …
ananyaa06 Aug 9, 2025
c78960d
feat: add testing coverage for secure-site.ts functions
ananyaa06 Aug 16, 2025
c99f4d9
refactor: run format
ananyaa06 Aug 16, 2025
f0361e7
feat: add test step to CI
ananyaa06 Aug 16, 2025
d737267
fix: make test action run before release
ananyaa06 Aug 16, 2025
488ea2e
refactor: remove old-sites
ananyaa06 Aug 28, 2025
c620d83
fix: put edit-sites behind secure
ananyaa06 Aug 28, 2025
2a99213
refactor: remove duplicate line
ananyaa06 Sep 7, 2025
ac44be1
refactor: remove empty line
ananyaa06 Sep 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docker-compose-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ services:

vis:
container_name: vis
image: ghcr.io/local-connectivity-lab/ccn-coverage-vis:latest
image: ccn-coverage-vis:latest
ports:
- "8090:80"
depends_on:
Expand All @@ -12,7 +12,7 @@ services:

api:
container_name: api
image: ghcr.io/local-connectivity-lab/ccn-coverage-api:latest
image: ccn-coverage-api:latest
ports:
- "8091:3000"
environment:
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
- MONGODB_URI=mongodb://mongodb:27017/api-data
- LDAP_URI=ldap://ldap:389
volumes:
- ./keys/:/app/keys
- ./keys/:/usr/src/app/keys/
depends_on:
- mongodb
- ldap
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { ldapRouter } from './routes/ldap-login';
import { newUserRouter } from './routes/new-user';
import { usersRouter } from './routes/users';
import { editSitesRouter } from './routes/edit-sites';
import { secureSitesRouter } from './routes/secure-site';
import { publicSitesRouter } from './routes/public-sites';
import logger from './logger';
import cors from 'cors';

Expand Down Expand Up @@ -72,6 +74,8 @@ app.use(ldapRouter);
app.use(newUserRouter);
app.use(usersRouter);
app.use(editSitesRouter);
app.use(secureSitesRouter);
app.use(publicSitesRouter);

process.on('SIGINT', async () => {
await mongoose.connection.close();
Expand Down
65 changes: 65 additions & 0 deletions src/models/site.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import mongoose from 'mongoose';
import { components } from '../types/schema';

type ISite = components['schemas']['Site'];

interface SiteDoc extends mongoose.Document, ISite {}

interface SiteModelInterface extends mongoose.Model<SiteDoc> {
build(attr: ISite): SiteDoc;
}

const siteSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
latitude: {
type: Number,
required: true,
},
longitude: {
type: Number,
required: true,
},
status: {
type: String,
enum: ['active', 'confirmed', 'in-conversation'],
required: true,
},
address: {
type: String,
required: true,
},
cell_id: {
type: [String],
required: true,
},
color: {
type: String,
required: false,
},
boundary: {
type: [[Number]],
validate: {
validator: function (val: number[][]) {
return val.every(pair => pair.length === 2);
},
message: 'Each boundary coordinate must be a pair of [latitude, longitude]',
},
required: false,
},
},
{
versionKey: false,
}
);

siteSchema.statics.build = (attr: ISite) => {
return new Site(attr);
};

const Site = mongoose.model<SiteDoc, SiteModelInterface>('Site', siteSchema);

export { Site, ISite, SiteDoc };
19 changes: 19 additions & 0 deletions src/routes/public-sites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import express, { Request, Response } from 'express';
import { Site } from '../models/site';

const router = express.Router();

router.get('/api/public-sites', async (req: Request, res: Response) => {
try {
const sites = await Site.find();

res.status(200).json({
sites: sites
});
} catch (error) {
console.error('Error retrieving public sites:', error);
res.status(500).send('Internal server error');
}
});

export { router as publicSitesRouter };
90 changes: 90 additions & 0 deletions src/routes/secure-site.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import express, { Request, Response } from 'express';
import { Site } from '../models/site';
// import connectEnsureLogin from 'connect-ensure-login';
import { components } from '../types/schema';

const router = express.Router();

type Site = components['schemas']['Site'];


router.put(
'/api/secure-site',
// connectEnsureLogin.ensureLoggedIn('/api/failure'),
async (req: Request, res: Response) => {
try {
const siteData: Site = req.body;

if (!siteData.name) {
res.status(400).json({ error: 'Site name is required' });
return;
}

const updatedSite = await Site.findOneAndUpdate(
{ name: siteData.name },
siteData,
{ new: true, runValidators: true }
);

if (!updatedSite) {
res.status(404).json({ error: 'Site not found' });
return;
}

res.status(200).json(updatedSite);
} catch (error: any) {
res.status(500).json({ error: 'Internal server error' });
}
}
);


router.post(
'/api/secure-site',
// connectEnsureLogin.ensureLoggedIn('/api/failure'),
async (req: Request, res: Response) => {
try {
const siteData: Site = req.body;

const newSite = Site.build(siteData);
const savedSite = await newSite.save();

res.status(201).json(savedSite);
} catch (error: any) {
if (error.name === 'ValidationError') {
res.status(400).json({ error: 'Validation error', details: error.message });
return;
}
res.status(500).json({ error: 'Internal server error' });
}
}
);


router.delete(
'/api/secure-site',
// connectEnsureLogin.ensureLoggedIn('/api/failure'),
async (req: Request, res: Response) => {
try {
const siteData: Site = req.body;

if (!siteData.name) {
res.status(400).json({ error: 'Site name is required' });
return;
}

const deletedSite = await Site.findOneAndDelete({ name: siteData.name });

if (!deletedSite) {
res.status(404).json({ error: 'Site not found' });
return;
}

res.status(200).json({ message: 'Site deleted successfully', site: deletedSite });
} catch (error: any) {
res.status(500).json({ error: 'Internal server error' });
}
}
);

export { router as secureSitesRouter };
Loading