Skip to content

Commit 46e32a5

Browse files
authored
Merge pull request #42 from darinalleman/8-toggle-pack-privacy
Add pack privacy switch
2 parents addc038 + e36043e commit 46e32a5

12 files changed

Lines changed: 119 additions & 12 deletions

File tree

api/controllers/pack.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import Sequelize from 'sequelize';
44
let router = express.Router();
55

66
import models from '../models';
7-
import { authenticate } from "../utils/jwt";
7+
import { authenticate, authenicatePublicRequest } from "../utils/jwt";
88
import { csvItems, packItemPayload, packPayload } from "../utils/build-payload";
99

1010
// Get
11-
router.get('/:id', async (req, res) => {
11+
router.get('/:id', authenicatePublicRequest, async (req, res) => {
1212
let { id } = req.params;
1313
models.Pack.findOne({
1414
where: { id },
@@ -21,7 +21,17 @@ router.get('/:id', async (req, res) => {
2121
{ model: models.User, attributes: ['id', 'username'] },
2222
]
2323
})
24-
.then(pack => res.json(pack))
24+
.then(pack => {
25+
if (pack.public) {
26+
res.json(pack);
27+
} else { //when the pack is private, only allow the owner of the pack to view
28+
if (req.user && pack.userId == req.user.id) {
29+
res.json(pack);
30+
} else {
31+
res.sendStatus(403); //return 'forbidden'
32+
}
33+
}
34+
})
2535
.catch(err => res.json(err));
2636
});
2737

api/models/pack.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const pack = (sequelize, DataTypes) => {
4343
},
4444
gender: {
4545
type: DataTypes.ENUM(Object.values(gender))
46+
},
47+
userId: {
48+
type: DataTypes.INTEGER
4649
}
4750
});
4851

api/utils/jwt.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,26 @@ export const authenticate = (req, res, next) => {
3636
next();
3737
})
3838
.catch(() => res.sendStatus(400));
39-
};
39+
};
40+
41+
/*
42+
This method will authenticate the user if one is logged in, but does not return a 400/401
43+
if there the user is not logged in to allow for public viewing of packs.
44+
This could also be used for public/private profiles.
45+
*/
46+
export const authenicatePublicRequest = (req, res, next) => {
47+
const authHeader = req.get('authorization');
48+
if (!authHeader) {
49+
req.user = undefined;
50+
next();
51+
} else {
52+
const token = authHeader.split(' ')[1];
53+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
54+
models.User.findOne({where: {id: decoded.id}})
55+
.then(user => {
56+
req.user = user;
57+
next();
58+
})
59+
.catch(() => res.sendStatus(400));
60+
}
61+
}

frontend/src/app/Pack/Pack.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ import Items from './Items';
2121
import { getWeightByCategory } from 'lib/utils/weight';
2222

2323
import { Credit, PackWrapper, SectionHeader, SectionTitle, TripDescription } from "./styles";
24+
import FullPageError from 'app/components/FullPageError';
2425

2526
const Pack: React.FC<PackSpecs.Props> = ({ getPack, weightUnit, packId }) => {
2627
const [loading, setLoading] = React.useState<boolean>(true);
28+
const [packForbidden, setPackForbidden] = React.useState<boolean>(true);
2729
const [pack, setPack] = React.useState<PackType | null>(null);
2830
const [unit, setUnit] = React.useState<WeightUnit>(weightUnit);
2931
const { dispatch } = useSidebar();
@@ -34,9 +36,16 @@ const Pack: React.FC<PackSpecs.Props> = ({ getPack, weightUnit, packId }) => {
3436
.then(pack => {
3537
setPack(pack);
3638
setLoading(false);
39+
setPackForbidden(false);
3740
})
38-
.catch(() => {
39-
alertError({ message: 'Unable to retrieve pack.' });
41+
.catch(e => {
42+
setLoading(false);
43+
if (e.message.includes("failed with status code 403")){//when forbidden to view
44+
setPackForbidden(true);
45+
}
46+
else {
47+
alertError({ message: 'Unable to retrieve pack.' });
48+
}
4049
});
4150

4251
return function cleanup() {
@@ -68,6 +77,10 @@ const Pack: React.FC<PackSpecs.Props> = ({ getPack, weightUnit, packId }) => {
6877
if (loading) {
6978
return <Loading size="large"/>
7079
}
80+
81+
if (packForbidden) {
82+
return <FullPageError text="This pack is private. If you're sure this is your pack, make sure you're logged in."></FullPageError>
83+
}
7184

7285
if (!pack) {
7386
return <p>Pack not found!</p>

frontend/src/app/PackForm/PackForm.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import Loading from "app/components/Loading";
2121
import { useSidebar } from "app/components/Sidebar/Context";
2222
import { NavigationConfirmModal } from 'react-router-navigation-confirm';
2323

24-
import { PageTitle, Controls, Box, Grid, PageDescription } from "styles/common";
24+
import { PageTitle, Controls, Box, Grid } from "styles/common";
25+
import SwitchInput from "app/components/FormFields/SwitchInput";
2526

2627
const PackForm: React.FC<PackFormSpecs.Props> = ({ history, packId, getPack, exportItems, getItems, createPack, updatePack, user }) => {
2728
const [loading, setLoading] = React.useState<boolean>(true);
@@ -119,7 +120,8 @@ const PackForm: React.FC<PackFormSpecs.Props> = ({ history, packId, getPack, exp
119120
duration_unit: packData ? packData.duration_unit : undefined,
120121
temp_range: packData ? packData.temp_range : '',
121122
season: packData ? packData.season : '',
122-
gender: packData ? packData.gender : undefined
123+
gender: packData ? packData.gender : undefined,
124+
public: packData ? packData.public : false
123125
}}
124126
validationSchema={Yup.object().shape({
125127
title: Yup.string().required("Trail name or location is required.")
@@ -244,6 +246,13 @@ const PackForm: React.FC<PackFormSpecs.Props> = ({ history, packId, getPack, exp
244246
/>
245247
</Col>
246248
</Row>
249+
<SwitchInput label="Pack Privacy"
250+
checked = {values.public}
251+
checkedText="Public"
252+
uncheckedText="Private"
253+
onChange={v => setFieldValue('public', v)}
254+
tip="When public, the pack will be viewable by anyone with a link"
255+
></SwitchInput>
247256
</div>
248257
</Grid>
249258
</Box>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as React from 'react';
2+
import { Switch } from 'antd';
3+
import { SharedInputProps } from "./types";
4+
import { InputContainer } from "./utils";
5+
6+
interface SwitchProps extends SharedInputProps {
7+
checked: boolean;
8+
checkedText?: string | undefined;
9+
uncheckedText?: string | undefined;
10+
onChange: (checked: boolean) => void;
11+
}
12+
13+
const SwitchInput: React.FC<SwitchProps> = ({ checked, checkedText, uncheckedText, onChange, label, tip, style }) => {
14+
const handleChange = (value: any) => onChange(value);
15+
return (
16+
<InputContainer {...{ label, tip, style }} labelStyle = {{display:"inline", paddingRight: '8px'}}>
17+
<Switch
18+
checked = {checked}
19+
checkedChildren={checkedText}
20+
unCheckedChildren={uncheckedText}
21+
onChange={handleChange}
22+
/>
23+
</InputContainer>
24+
);
25+
};
26+
27+
export default SwitchInput;

frontend/src/app/components/FormFields/styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,4 @@ export const CharacterCounter = styled.small`
115115
&.full {
116116
color:red;
117117
}
118-
`;
118+
`;

frontend/src/app/components/FormFields/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ export interface SharedInputProps {
1515
tip?: string;
1616
last?: boolean;
1717
style?: CSSProperties;
18+
labelStyle?: CSSProperties;
1819
onBlur?: () => void;
1920
}

frontend/src/app/components/FormFields/utils.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ const tooltip = (tip: string) => (
1818
</Tooltip>
1919
);
2020

21-
export const InputContainer: React.FC<SharedInputProps> = ({ label, errorMsg, error, tip, last, style, children }) => (
21+
export const InputContainer: React.FC<SharedInputProps> = ({ label, errorMsg, error, tip, last, style, children, labelStyle }) => (
2222
<Row className={last ? 'last' : ''} style={style}>
2323
{label && (
24-
<Label>
24+
<Label style={labelStyle}>
2525
{label}
2626
{tip && tooltip(tip)}
2727
</Label>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as React from 'react';
2+
import { PageDescription, PageTitle } from 'styles/common';
3+
4+
interface FullPageErrorProps {
5+
text?: string;
6+
styles? : React.CSSProperties;
7+
}
8+
9+
const FullPageError: React.FC<FullPageErrorProps> = ({ text, styles }) => {
10+
const defaultStyles: React.CSSProperties = {};
11+
12+
return (
13+
<div style={defaultStyles}>
14+
<PageTitle><h1>Oops!</h1></PageTitle>
15+
<PageDescription><p>{text}</p></PageDescription>
16+
</div>
17+
)
18+
};
19+
20+
export default FullPageError;

0 commit comments

Comments
 (0)