text:
<file_sep># Configure Spree Preferences
#
# Note: Initializing preferences available within the Admin will overwrite any changes that were made through the user interface when you restart.
#       If you would like users to be able to update a setting with the Admin it should NOT be set here.
#
# In order to initialize a setting do:
# config.setting_name = 'new value'
Spree.config do |config|
  # Example:
  # Uncomment to override the default site name.
  config.site_name = "Chosenlight Shop"
  #config.allow_ssl_in_production = false
  config.attachment_url = ":s3_sg_url"
  config.s3_host_alias = "s3-ap-southeast-1.amazonaws.com"
  config.default_country_id = Spree::Country.find_by_iso("ID").id
end

Spree.user_class = "Spree::User"

#S3 Support
Paperclip.interpolates(:s3_sg_url) do |attachment, style|
  "#{attachment.s3_protocol}://#{Spree::Config[:s3_host_alias]}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/},"")}"
end

repo_language: Ruby

repo_name: pampie/store


text:
<repo_name>MatthewVicente/TypeScriptLessons<file_sep>/features/arrays.ts
const carMakers: string[] = ['ford', 'toyota', 'chevy'];
const dates = [new Date(), new Date()];
const carsByMakers: string[][] = [];

// Help with inference when extracting values
const carMarker = carMakers[0];
const myCar = carMakers.pop();

// Prevent incompatible values
carMakers.push(100);

// Help with 'map'
carMakers.map((car:string): string => {
    return car.toUpperCase();
});

// Flexible types
const importantDates: (Date | string)[] = [];
const today = new Date();
const dateOne = '2030-10-10';

branch_name: refs/heads/master

repo_language: TypeScript

repo_name: MatthewVicente/TypeScriptLessons


text:
<repo_name>paulcsmith/crecto<file_sep>/spec/migrations/sqlite3_migrations.sql
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS users_different_defaults;
DROP TABLE IF EXISTS users_large_defaults;
DROP TABLE IF EXISTS posts;
DROP TABLE IF EXISTS addresses;
DROP TABLE IF EXISTS user_projects;
DROP TABLE IF EXISTS projects;

CREATE TABLE users(
  id INTEGER NOT NULL PRIMARY KEY,
  name varchar(255) NOT NULL,
  things integer,
  stuff integer,
  nope float,
  yep bool,
  pageviews bigint,
  some_date DATETIME,
  created_at DATETIME,
  updated_at DATETIME
);

CREATE UNIQUE INDEX users_4ijlkjdf ON users (id);

CREATE TABLE users_different_defaults(
  user_id INTEGER NOT NULL PRIMARY KEY,
  name varchar(255) NOT NULL,
  xyz DATETIME
);

CREATE UNIQUE INDEX users_different_defaults_kljl3kj on users_different_defaults (user_id);

/* INTEGER can store big numbers, this is the only type that supports
   auto_increment ROWID*/
CREATE TABLE users_large_defaults(
  id INTEGER NOT NULL PRIMARY KEY,
  name varchar(255) NOT NULL
);

CREATE UNIQUE INDEX users_4asdf ON users_large_defaults (id);

CREATE TABLE posts(
  id INTEGER NOT NULL PRIMARY KEY,
  user_id INTEGER references users(id),
  created_at DATETIME,
  updated_at DATETIME
);

CREATE UNIQUE INDEX posts_df8sdd ON posts (id);

CREATE TABLE addresses(
  id INTEGER NOT NULL PRIMARY KEY,
  user_id INTEGER references users(id),
  created_at DATETIME,
  updated_at DATETIME
);

CREATE UNIQUE INDEX addresses_dfd7fs7ss ON addresses (id);

CREATE TABLE projects(
  id INTEGER NOT NULL PRIMARY KEY,
  name VARCHAR(255),
  created_at DATETIME,
  updated_at DATETIME
);

CREATE UNIQUE INDEX projects_88fsssfsf ON projects (id);

CREATE TABLE user_projects(
  id INTEGER NOT NULL PRIMARY KEY,
  user_id INTEGER references users(id),
  project_id INTEGER references projects(id),
  created_at DATETIME,
  updated_at DATETIME
);

CREATE UNIQUE INDEX user_projects_dd8dfss ON user_projects (id);

repo_language: SQL

repo_name: paulcsmith/crecto


text:
<repo_name>josehbez/cargamos<file_sep>/app/warehouse/models.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors

from app import db, commun

class Warehouse(commun.BaseModel):

    __tablename__ = "warehouse"

    address = db.Column(db.String(250), nullable=False)

    def __init__(self, name, address) -> None:
        self.name = name
        self.address = address

    def serialize(self):

        return {
            'id': self.id,
            'name': self.name,
            'address': self.address,
        }

    @staticmethod
    def by(**kwargs):
        return Warehouse.query.filter_by(**kwargs).first_or_404(description='Record with {} is not available'.format(str(kwargs)))

    @staticmethod
    def all():
        return Warehouse.query.all()
    <file_sep>/migrations/versions/09ecc0f43610_.py
"""empty message

Revision ID: 09ecc0f43610
Revises: <PASSWORD>
Create Date: 2021-03-21 23:18:57.025648

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '<KEY>'
down_revision = '<PASSWORD>'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('product',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=50), nullable=False),
    sa.Column('date_created', sa.DateTime(), nullable=True),
    sa.Column('date_modified', sa.DateTime(), nullable=True),
    sa.Column('sku', sa.String(length=250), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('sku')
    )
    op.create_table('stock_move',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=50), nullable=False),
    sa.Column('date_created', sa.DateTime(), nullable=True),
    sa.Column('date_modified', sa.DateTime(), nullable=True),
    sa.Column('type', sa.String(length=10), nullable=False),
    sa.Column('qty', sa.Integer(), nullable=False),
    sa.Column('product_id', sa.Integer(), nullable=False),
    sa.Column('warehouse_id', sa.Integer(), nullable=False),
    sa.ForeignKeyConstraint(['product_id'], ['product.id'], ),
    sa.ForeignKeyConstraint(['warehouse_id'], ['warehouse.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('stock_move')
    op.drop_table('product')
    # ### end Alembic commands ###
<file_sep>/app/stock/models.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors

from app import db, commun

class StockMove(commun.BaseModel):

    __tablename__ = "stock_move"

    type = db.Column(db.String(10), nullable=False)
    qty = db.Column(db.Integer, nullable=False)
    product_id = db.Column(db.Integer, db.ForeignKey("product.id"), nullable=False)
    warehouse_id = db.Column(db.Integer, db.ForeignKey("warehouse.id"), nullable=False)

    def __init__(self, name, type, qty, product_id, warehouse_id):
        self.name = name
        self.type = type
        self.qty = qty
        self.product_id = product_id
        self.warehouse_id = warehouse_id

    def serialize(self):
        return {
            'name': self.name,
            #'type': self.type,
            'qty': self.qty,
            'product_id': self.product_id,
            'warehouse_id': self.warehouse_id
        }

    @staticmethod
    def by(**kwargs):
        return StockMove.query.filter_by(**kwargs).first_or_404(description='Record with {} is not available'.format(str(kwargs)))

    @staticmethod
    def all():
        return StockMove.query.all()

def next_move(ttype):
    """
        Return stock move name by type.
        format: TYPE/NEXT_NUMBER (e.g SO/1 or PO/1)
    """
    count = db.session.query(StockMove.id).count() + 1
    return str('SO/' if ttype =='sale' else 'PO/') + str(count)

def stock_all():
    return _get_stock()

def stock_product(product_id:int):
    filter = " where product_id=%s " % product_id
    return _get_stock( filter=filter)

def stock_warehouse(warehouse_id:int):
    filter = " where warehouse_id=%s " %  warehouse_id
    return _get_stock( filter=filter)

def stock_product_warehouse(product_id:int, warehouse_id:int):
    filter = " where product_id=%s and warehouse_id=%s " % (product_id, warehouse_id)
    return _get_stock( filter=filter)

def _get_stock(filter=""):

    query="""
        SELECT
            T3.qty,
            T3.product_id,
            product.name product_name,
            T3.warehouse_id,
            warehouse.name warehouse_name
        FROM(
            SELECT
                sum(T2.qty) qty,
                T2.product_id,
                T2.warehouse_id
            FROM (
                SELECT
                    T1.type,
                    CASE WHEN T1.type = 'out' THEN T1.qty *-1
                    ELSE T1.qty END AS qty,
                    T1.product_id, T1.warehouse_id
                from  (
                    select
                        type, sum(qty) qty,
                        product_id, warehouse_id
                    from stock_move %s
                    GROUP by type, product_id, warehouse_id
                )T1
            )T2 GROUP by  T2.product_id, T2.warehouse_id
        )T3
            INNER JOIN product on product.id =T3.product_id
            INNER JOIN warehouse on warehouse.id= T3.warehouse_id
        where T3.qty > 0;
    """ % filter
    rows = []
    try:
        result = db.engine.execute(query)
        for row in result:
            rows.append({
                'qty': int(row[0]),
                'product_id': row[1],
                'product_name': row[2],
                'warehouse_id': row[3],
                'warehouse_name': row[4],
            })
        if len(rows) == 0:
            raise Exception("Stock not found")
    except Exception as e:
        return e
    return rows<file_sep>/run.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors

from app import app

app.run(host='0.0.0.0', port=8844)<file_sep>/app/stock/stock_resources.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
from app.stock.models import stock_all, stock_product_warehouse,\
    stock_product, stock_warehouse
from app.commun import rp, BaseResource, is_int

class Stock(BaseResource):

    def get(self, product_id:int=0, warehouse_id:int=0):

        if product_id>0 and warehouse_id>0:
            rows = stock_product_warehouse(product_id, warehouse_id)
        else:
            rows = stock_all()

        if isinstance(rows, Exception):
            return rp(message=str(rows)), 404

        return rp(success=True,payload=rows), 200

class StockProduct(BaseResource):

    def get(self, product_id:int=0):

        if product_id>0:
            rows = stock_product(product_id)
        else:
            return rp(message="The product ID must be an integer and greate zero"), 404

        if isinstance(rows, Exception):
            return rp(message=str(rows)), 404

        return rp(success=True,payload=rows), 200

class StockWarehouse(BaseResource):

    def get(self, warehouse_id:int=0):

        if warehouse_id>0:
            rows = stock_warehouse(warehouse_id)
        else:
            return rp(message="The warehouse ID must be an integer and greate zero"), 404

        if isinstance(rows, Exception):
            return rp(message=str(rows)), 404

        return rp(success=True,payload=rows), 200<file_sep>/Dockerfile
FROM python:3.6

ENV MYHOME=/cargamos

WORKDIR ${MYHOME}

COPY ./ ${MYHOME}

RUN pip install --user --upgrade pip
RUN pip install --user -r requirements.txt

ENTRYPOINT ["python"]
EXPOSE 8844
CMD ["run.py"]<file_sep>/test.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
import unittest
import pdb
from app import app, commun, db
import random

class AppTestCase(unittest.TestCase):

    randid = lambda self: str(random.randint(1, 10000000))
    headers= {}

    def setUp(self) -> None:
        self.app = app
        self.client = self.app.test_client
        self.product_id = 0
        self.warehouse_id= 0
        db.create_all()

    def test_auth(self):
        id = self.randid()
        data = {
            'name': 'Name %s' % id,
            'email': 'e%s@d%s.com' %(id, id) ,
            'password': 'p%s' % id,
        }
        res = self.client().post('/v1/auth/register', data=data)
        self.assertEqual(res.status_code, 201, res.data)

        res = self.client().post('/v1/auth/register', data=data)
        self.assertEqual(res.status_code, 500, res.data)

        res = self.client().post('/v1/auth/login', data=data)
        self.assertEqual(res.status_code, 201, res.data)

        self.headers.update({'Authorization':'Bearer '+ res.json.get('payload').get('token')})

    def test_warehouse(self):
        if self.warehouse_id != 0:
            return
        id = self.randid()
        data = {
            'name': "WH/%s" % id,
            'address': "Address %s" % id
        }

        res = self.client().post('/v1/warehouse', data=data, headers=self.headers)
        self.assertEqual(res.status_code, 201, res.data)

        data = res.json.get('payload')

        self.warehouse_id = data.get('id')

        res = self.client().get('/v1/warehouse', headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

        res = self.client().get('/v1/warehouse/%s' % data.get('id'), headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

        data.update({'name': "%s Update" % data.get('name')})
        res = self.client().put('/v1/warehouse/%s' % data.get('id'), data=data,headers=self.headers)
        self.assertEqual(res.status_code, 201, res.data)

        #res = self.client().delete('/v1/warehouse/%s' % data.get('id'), headers=self.headers)
        #self.assertEqual(res.status_code, 204, res.data)

    def test_product(self):
        if self.product_id != 0:
            return
        id = self.randid()
        data = {
            'name': "Name %s" % id,
            'sku': "SKU%s" % id
        }
        res = self.client().post('/v1/product', data=data, headers=self.headers)
        self.assertEqual(res.status_code, 201, res.data)
        data = res.json.get('payload')

        self.product_id = data.get('id')

        res = self.client().post('/v1/product', data=data, headers=self.headers)
        self.assertEqual(res.status_code, 500, res.data)

        res = self.client().get('/v1/product', headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

        res = self.client().get('/v1/product/%s' % data.get('id'), headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

        res = self.client().get('/v1/product/%s' % data.get('sku'), headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

        data.update({'name': "%s Update" % data.get('name')})
        res = self.client().put('/v1/product/%s' % data.get('id'), data=data,headers=self.headers)
        self.assertEqual(res.status_code, 201, res.data)

        #res = self.client().delete('/v1/product/%s' % data.get('id'), headers=self.headers)
        #self.assertEqual(res.status_code, 204, res.data)

    def req_wp(self):
        self.test_product()
        self.test_warehouse()

    def test_purchase(self):
        self.req_wp()
        data = {
            'qty': 100,
            'product_id': self.product_id,
            'warehouse_id': self.warehouse_id,
        }
        res = self.client().post('/v1/purchase', data=data, headers=self.headers)
        self.assertEqual(res.status_code, 201, res.data)

    def test_stock(self):
        self.test_sale()
        res = self.client().get('/v1/stock', headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

        res = self.client().get('/v1/stock/%s/%s'%(self.product_id, self.warehouse_id), headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

        res = self.client().get('/v1/stock/product/%s'% self.product_id, headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

        res = self.client().get('/v1/stock/warehouse/%s'% self.warehouse_id, headers=self.headers)
        self.assertEqual(res.status_code, 200, res.data)

    def test_sale(self):
        self.test_purchase()
        data = {
            'qty': 23,
            'product_id': self.product_id,
            'warehouse_id': self.warehouse_id,
        }
        res = self.client().post('/v1/sale', data=data, headers=self.headers)
        self.assertEqual(res.status_code, 201, res.data)

if __name__ == '__main__':
    unittest.main()
<file_sep>/.pm/changelog.yml
"2021-03-20":
- Initialized pm (Project Metadata)
<file_sep>/.github/workflows/build-deploy.yml

name: Build and Deploy

on: [push]

jobs:

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1

      - name: Unittest
        run: |
          python -m pip install --upgrade pip
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
          export APP_SETTINGS=test
          python test.py

      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: jhbez/cargamos:latest

      - name: Deploy Production
        uses: appleboy/ssh-action@master
        with:
          password: ${{ <PASSWORD> }}
          host: ${{ secrets.DEPLOY_IP }}
          username: ${{ secrets.DEPLOY_USER }}
          port: 22
          script: |
            [[ ! -d cargamos ]] && git clone https://github.com/josehbez/cargamos.git
            [[ -d cargamos ]] && cd cargamos; git pull
            export APP_SETTINGS=production
            docker-compose -f /mnt/cargamos/docker-compose.yml stop || true
            docker rm cargamos || true
            docker-compose -f /mnt/cargamos/docker-compose.yml up --build -d
            docker container  exec -t cargamos python manage.py db upgrade
<file_sep>/app/auth/resources.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
from flask import request
from app.commun import rp, BaseResource
from flask_restful import reqparse
from app.auth.models import User as UserModel
from werkzeug.security import generate_password_hash, check_password_hash
import jwt
import datetime
from app import app

def jwt_required():
    authorization = request.headers.get('Authorization','')[7:]
    path = request.path
    exclude =[
        '/v1/auth/login',
        '/v1/auth/register',
        '/',
        '',
    ]
    if path not in exclude:
        try:
            payload = jwt.decode(authorization, app.config.get('SECRET_KEY'))
            UserModel.by(id=payload['sub'])
        except jwt.ExpiredSignatureError:
            return rp(message='Signature expired. Please log in again.')
        except jwt.InvalidTokenError:
            return rp(message='Invalid token. Please log in again.')
        except Exception as e:
            return rp(message=str(e))

    return None

def current_identity():
    authorization = request.headers.get('Authorization','')[7:]
    payload = jwt.decode(authorization, app.config.get('SECRET_KEY'))
    return UserModel.by(id=payload['sub'])

class Register(BaseResource):

    def reqparse(self):
        post_parse = reqparse.RequestParser()
        post_parse.add_argument('name', dest='name', required=True,
            help="The user's name")
        post_parse.add_argument('email', dest='email', required=True,
            help="The user's email")
        post_parse.add_argument('password', dest='password', required=True,
            help="The user's email")

        return post_parse.parse_args()

    def post(self):
        args = self.reqparse()
        row = UserModel(args.name, args.email, generate_password_hash(args.password))
        err = row.save()
        if err != None:
            return rp(message=str(err)), 500
        return rp(success=True, payload=row.serialize()), 201

class Login(BaseResource):

    def reqparse(self):
        post_parse = reqparse.RequestParser()
        post_parse.add_argument('email', dest='email', required=True,
            help="The user's email")
        post_parse.add_argument('password', dest='password', required=True,
            help="The user's email")

        return post_parse.parse_args()

    def post(self):
        args = self.reqparse()
        user = UserModel.by(email=args.email)

        if check_password_hash(user.password, args.password):
            jwt_payload = {
                'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30, seconds=5),
                'iat': datetime.datetime.utcnow(),
                'sub': user.id
            }
            try:
                token = jwt.encode(jwt_payload, app.config.get('SECRET_KEY'), algorithm='HS256')
                user.token = token.decode()
                err = user.update()
                if err != None:
                    raise err
                resp = rp(success=True, payload=user.serialize()), 201
            except Exception as e:
                resp = rp(message=str(e)), 500
        else:
            resp = rp(message='The passsword is incorrect'), 404

        return resp<file_sep>/app/stock/stock_move_resources.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
from flask_restful import reqparse
from app.stock.models import StockMove as StockMoveModel, \
    next_move, stock_product_warehouse
from app.product.models import Product as ProductModel
from app.warehouse.models import Warehouse as WarehouseModel
from app import db
from app.commun import rp, BaseResource, is_int

def stock_move_reqparse():
    post_parse = reqparse.RequestParser()
    post_parse.add_argument('qty', type=int, dest='qty', required=True,
        help="The quantity")
    post_parse.add_argument('product_id', type=int, dest='product_id', required=True,
        help="The product id")
    post_parse.add_argument('warehouse_id', type=int, dest='warehouse_id', required=True,
        help="The warehouse id")
    return post_parse.parse_args()

def stock_move_transfer(ttype):
    args = stock_move_reqparse()
    if args.qty <= 0:
        return rp(message="Quantity must be an integer and greater to zero"), 404
    if args.product_id <= 0:
        return rp(message="Product ID must be an integer. Check /VERSION/product"), 404
    if args.warehouse_id <= 0:
        return rp(message="Warehouse ID must be an integer. Check /VERSION/warehouse"), 404

    product_id = ProductModel.by(id=args.product_id)
    warehouse_id = WarehouseModel.by(id=args.warehouse_id)

    if ttype == 'sale':
        stock = stock_product_warehouse(
            product_id=product_id.id,
            warehouse_id=warehouse_id.id)
        if isinstance(stock, Exception):
            return rp(message=str(stock)), 400
        product_stock = stock[0].get('qty')
        if int(args.qty) > product_stock:
            return rp(
                message="The product %s only has %s stock" % (product_id.id, product_stock),
                ), 400
        type_move = 'out'
    else: # defautl is purchase
        type_move = 'in'

    name = next_move(ttype)

    stock_move = StockMoveModel(name, type_move, args.qty,
        product_id=product_id.id, warehouse_id=warehouse_id.id)

    err =stock_move.save()

    if err != None:
        return rp(message=str(err)), 500

    return rp(success=True, payload=stock_move.serialize()), 201

class Sale(BaseResource):

    def post(self):
        return stock_move_transfer('sale')

class Purchase(BaseResource):

    def post(self):
        return stock_move_transfer('purchase')

<file_sep>/app/commun.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
from typing import Any
from flask_restful import Resource
from . import db
import json

def ppjson(s):
    """
        pretty print json
    """
    print(json.dumps(s, indent=4))

def is_int(s):
    """
        Check value is integer using cast
        params: string value
        return: boolean
    """
    try:
        int(s)
        return True
    except ValueError:
        return False

def rp(success=False, message=None, payload=None):
    """
        rp (aka, response payload) return standard payload
        params: success=boolean, message=string|None, payload=dict|None
        return: dict
    """
    return{
        'success': success,
        'message': message,
        'payload': payload,
    }

class BaseResource(Resource):
    """
        RESTful resource base class
    """
    def reqparse(self) -> Any:
        raise NotImplementedError

class BaseModel(db.Model):
    """
        SQLAlchemy model bastract class
    """
    __abstract__ = True

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(50), nullable=False)

    date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
    date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(),
                                            onupdate=db.func.current_timestamp())

    def __repr__(self) -> str:
        return '<%s %s> %s' % (self.__class__.__name__, self.id, self.name)

    def save(self):
        try:
            db.session.add(self)
            db.session.commit()
        except Exception as e:
            return e
        return None

    def update(self):
        try:
            db.session.commit()
        except Exception as e:
            return e
        return None

    def delete(self):
        try:
            db.session.delete(self)
            db.session.commit()
        except Exception as e:
            return e
        return None

    def serialize(self):
        raise NotImplementedError

    @staticmethod
    def by(**kwargs):
        raise NotImplementedError

    @staticmethod
    def all():
        raise NotImplementedError<file_sep>/app/product/resources.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
from flask_restful import reqparse
from app.product.models import Product as ProductModel
from app.commun import rp, BaseResource, is_int

class Product(BaseResource):

    def reqparse(self):
        post_parse = reqparse.RequestParser()
        post_parse.add_argument('name', dest='name', required=True,
            help="The product's name")
        post_parse.add_argument('sku', dest='sku', required=True,
            help="The product's sku")

        return post_parse.parse_args()

    def get(self, id=None):
        if id is not None:
            if is_int(id):
                rows = ProductModel.by(id=int(id)).serialize()
            else:
                rows = ProductModel.by(sku=id).serialize()
        else:
            rows = [ a.serialize() for a in ProductModel.all()]
        return rp(success=True, payload=rows)

    def post(self):
        args = self.reqparse()
        row = ProductModel(args.name, args.sku)
        err = row.save()
        if err != None:
            res = rp(message=str(err)), 500
        else:
            res = rp(success=True, payload=row.serialize()), 201
        return res

    def put(self, id):
        args = self.reqparse()
        if not is_int(id ):
            return rp(message="The ID must be an integer"), 404

        row = ProductModel.by(id=int(id))
        row.name= args.name
        row.sku= args.sku
        err = row.update()
        if err != None:
            res = rp(message=str(err)), 500
        else:
            res = rp(success=True, payload=row.serialize()), 201
        return res

    def delete(self, id):
        if not is_int(id ):
            return rp(message="The ID must be an integer"), 404
        row = ProductModel.by(id=int(id))
        err = row.delete()
        if err != None:
            res = rp(message=str(err)), 500
        else:
            res = rp(success=True, payload=row.serialize()), 204
        return res<file_sep>/app/product/models.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors

from app import db, commun

class Product(commun.BaseModel):

    __tablename__ = "product"

    sku = db.Column(db.String(250), nullable=False, unique=True)

    def __init__(self, name, sku):
        self.name = name
        self.sku = sku

    def serialize(self):

        return {
            'id': self.id,
            'name': self.name,
            'sku': self.sku,
        }

    @staticmethod
    def by(**kwargs):
        return Product.query.filter_by(**kwargs).first_or_404(description='Record with {} is not available'.format(str(kwargs)))

    @staticmethod
    def all():
        return Product.query.all()<file_sep>/app/__init__.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
import os
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from flask_restful import Api
import os

app = Flask(__name__)

# Load config
config_name = os.getenv('APP_SETTINGS', 'dev')
if not len(config_name):
    config_name='dev'

app.config.from_json("../deployments/%s/config.json" %  config_name)

# DB manager
db = SQLAlchemy(app)

# API manager
api = Api(app, prefix='/v1')

def api_load_resources():
    from app.auth.resources import Register, Login
    api.add_resource(Register, "/auth/register")
    api.add_resource(Login, "/auth/login")

    from app.warehouse.resources import Warehouse
    api.add_resource(Warehouse, "/warehouse", "/warehouse/<int:id>")

    from app.product.resources import Product
    api.add_resource(Product, "/product", "/product/<string:id>")

    from app.stock.stock_move_resources import Purchase, Sale
    api.add_resource(Purchase, "/purchase")
    api.add_resource(Sale, "/sale")

    from app.stock.stock_resources import Stock, StockProduct, StockWarehouse
    api.add_resource(Stock, "/stock", "/stock/<int:product_id>/<int:warehouse_id>")
    api.add_resource(StockProduct, "/stock/product/<int:product_id>")
    api.add_resource(StockWarehouse, "/stock/warehouse/<int:warehouse_id>")

api_load_resources()

@app.route("/")
def index():
    return render_template("index.html", environment=config_name)

@app.before_request
def _jwt_required():
    from app.auth.resources import jwt_required
    err = jwt_required()
    if err != None:
        return err, 404

#db.create_all()<file_sep>/app/warehouse/resources.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
from flask_restful import reqparse
from app.warehouse.models import Warehouse as WarehouseModel
from app.commun import rp, BaseResource

class Warehouse(BaseResource):

    def reqparse(self):
        post_parse = reqparse.RequestParser()
        post_parse.add_argument('name', dest='name', required=True,
            help="The warehouse's name")
        post_parse.add_argument('address', dest='address', required=True,
            help="The warehouse's address")

        return post_parse.parse_args()

    def get(self, id:int=0):
        if id > 0:
            rows = WarehouseModel.by(id=id).serialize()
        else:
            rows = [ a.serialize() for a in WarehouseModel.all()]
        return rp(success=True, payload=rows)

    def post(self):
        args = self.reqparse()
        row = WarehouseModel(args.name, args.address)
        err = row.save()
        if err != None:
            res = rp(message=str(err)), 500
        else:
            res = rp(success=True, payload=row.serialize()), 201
        return res

    def put(self, id:int):
        args = self.reqparse()
        row = WarehouseModel.by(id=id)
        row.name= args.name
        row.address= args.address
        err = row.update()
        if err != None:
            res = rp(message=str(err)), 500
        else:
            res = rp(success=True, payload=row.serialize()), 201
        return res

    def delete(self, id:int):
        row = WarehouseModel.by(id=id)
        err = row.delete()
        if err != None:
            res = rp(message=str(err)), 500
        else:
            res = rp(success=True, payload=row.serialize()), 204
        return res<file_sep>/README.md
<p align="center">
    <a href="LICENSE">
        <img src="https://img.shields.io/github/license/josehbez/cargamos?style=flat-square" />
    </a>
    <a href=".pm/version.yml">
        <img src="https://img.shields.io/badge/dynamic/yaml?color=green&label=version&query=version.*&url=https://raw.githubusercontent.com/josehbez/cargamos/master/.pm/version.yml"/>
    </a>
    <a href=".pm/version.yml">
        <img src="https://img.shields.io/badge/dynamic/yaml?color=green&label=prerelease&query=prerelease.*&url=https://raw.githubusercontent.com/josehbez/cargamos/master/.pm/version.yml"/>
    </a href="#">
        <img src="https://github.com/josehbez/cargamos/workflows/Build%20and%20Deploy/badge.svg"/>
    <a>
</a>
</p>

# Cargamos

Simple gestor de inventario desarrollado sobre FLASK, FLASK-RESTFUL, FLASK-SQLALCHEMY,

## Tabla de contenido

* Instalación
* Diseño DB
* Diseño RESTful
  * Url demo
  * Documentacion en Postman
  * Demo en Postman
* Tests

## Instalación

```bash
# Configuración definido por: APP_SETTINGS
# Puede tener los siguientes valores: dev | test | production
# El archivo config.json debe ser colocado en deployments/${APP_SETTINGS}/config.json
#
# Variables importantes del archivo config.json
#
# SQLALCHEMY_DATABASE_URI - Conector base dedatos
# e.g: postgresql://USERNAME:PASSWORD@HOST/DBNAME
# e.g: sqlite:///../storage/cargamos.db
#
# SECRET_KEY - Llave de la configuración
# e.g: \\x83\\xb8\\xf0\\x9e%\\x1f\\xd9\\xf2\\xf6\\x8b\\x04"\\x886Pjz\\x9f2M

################## pipenv ##################
# Copiar plantilla deployments/testing/config.json
# Pegar el archivo en deployments/dev/config.json

pipenv shell
pip install -r requirements.txt
export APP_SETTINGS=dev
python manage.py db upgrade
python run.py

################## docker-compose ##################
# Revisar plantilla deployments/production/config.json

export APP_SETTINGS=production
docker-compose up --build -d
docker container  exec -t cargamos python manage.py db upgrade

################## Puerto 8844 ##################
* Serving Flask app "app" (lazy loading)
* Running on http://0.0.0.0:8844/ (Press CTRL+C to quit)
```

## Diseño DB

```bash
Todas las tablas heredan de una clase abstracta y tienen los siguientes campos:
1.- name - Nombre del movimiento, transaccion ó usuario
2.- id - Identificador del registro
3.- date_created - Fecha de creación del registro
4.- date_modified - Fecha de la ultima modificación del registro

Tablas:

1.- USERS - Se utiliza para el manejo de autenticaciones
	* email - Correo del usuario
	* password - <PASSWORD> encript<PASSWORD>
	* token - Token de authorización para peticiones HTTP

2.- WAREHOUSE - Se utiliza para registrar almacenes (tiendas)
	* address - Dirección del almacén

3.- PRODUCT - Se utiliza para registrar productos
	* sku - unidad de mantenimiento en almacén del producto

4.- STOCK_MOVE - Se utiliza para registrar movimientos de almacén
	* type - Tipo de movimiento entrda (in) o salida (out)
	* qty - Cantidad de movmiento
	* product_id - ID del producto
	* warahouse_id - ID del almacén

Vistas(consultas):

1.- STOCK - manejo de existencias
	* qry - Cantidad de existencias
	* product_id - ID del producto
	* warehouser - ID del almacén

Ejemplo:

-------------------users------------------
| id | name | email   | password | token |
| 1  | jose | <EMAIL> | sha256   | j.w.t |

------warehouse--------
| id | name | address |
| 1  | WH1  | GDL     |
| 2  | WH2  | CDMX    |

--------product------
| id | name   | sku |
| 1  | iPhone | I2  |
| 1  | iPad   | I6  |

----------------------stock_move----------------------
| id | type | qty | name | product_id | warehouse_id |
| 1  | in   | 93  | PO/1 | 1          | 1            |
| 1  | out  | 3   | SO/2 | 1          | 1            |
| 1  | in   | 20  | PO/3 | 2          | 2            |

---------------stock--------------
| qty | product_id | warehouse_id |
| 90  | 1          | 1            |
| 20  | 2          | 2            |

```

## Diseño RESTful

### URL

http://demo.josehbez.com

### Documentación

https://documenter.getpostman.com/view/5247124/TzCFfqC8

### Demo

[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/ca919d4646123590d6ea#?env%5BStaging%5D=<KEY>)

```bash
# Auth:

POST /v1/auth/register HTTP/1.1
POST /v1/auth/login HTTP/1.1

# Warehouse
POST /v1/warehouse HTTP/1.1
PUT /v1/warehouse/{{WH_ID}} HTTP/1.1
GET /v1/warehouse HTTP/1.1
DELETE /v1/warehouse/{{WH_ID}} HTTP/1.1

# Product
POST /v1/product HTTP/1.1
PUT /v1/product/{{PRODUCT_ID}} HTTP/1.1
GET /v1/product HTTP/1.1
DELETE /v1/product/{{PRODUCT_ID}} HTTP/1.1

# Stock Move
POST /v1/purchase HTTP/1.1
POST /v1/sale HTTP/1.1

# Stock
GET /v1/stock HTTP/1.1
GET /v1/stock/product/{{PRODUCT_ID}} HTTP/1.1
GET /v1/stock/warehouse/{{WH_ID}} HTTP/1.1
GET /v1/stock/{{PRODUCT_ID}}/{{WH_ID}} HTTP/1.1

```

## Tests

```bash
export APP_SETTINGS=test
python test.py
```<file_sep>/requirements.txt
Flask==1.1.2
Flask-RESTful==0.3.8
Flask-SQLAlchemy==2.5.1
Flask-Migrate==2.7.0
psycopg2-binary==2.8.6
Flask-Script==2.0.6
PyJWT==1.4.2<file_sep>/app/warehouse/__init__.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors
<file_sep>/app/auth/models.py
# -*- coding: utf-8 -*-
#  Copyright (c) 2021 The Project Cargamos Authors

from app import db, commun

class User(commun.BaseModel):

    __tablename__ = "users"

    email = db.Column(db.String(50), nullable=False, unique=True)
    password = db.Column(db.String(250), nullable=False)

    token = db.Column(db.String(250), nullable=True)

    def __init__(self, name, email, password) -> None:
        self.name = name
        self.email = email
        self.password = <PASSWORD>

    def serialize(self):
        return {
            'name':self.name,
            'email':self.email,
            'token':self.token,
        }

    @staticmethod
    def by(**kwargs):
        return User.query.filter_by(**kwargs).first_or_404(description='Record with %s is not available'.format(str(kwargs)))

languages: Markdown Dockerfile YAML Python Text

repo_language: Markdown

repo_name: josehbez/cargamos


text:
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LittleBird.UI.Areas.Member.Models.VM
{
    public class AddTweetVM
    {
    }
}<file_sep>using LittleBird.Model.Option;
using LittleBird.Service.Option;
using LittleBird.UI.Areas.Admin.Models.DTO;
using LittleBird.UI.Areas.Admin.Models.VM;
using LittleBird.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace LittleBird.UI.Areas.Admin.Controllers
{
    public class TweetController : Controller
    {
        TweetService _tweetService;
        AppUserService _appUserService;
        CommentService _commentService;

        public TweetController()
        {
            _tweetService = new TweetService();
            _appUserService = new AppUserService();
            _commentService = new CommentService();
        }
        public ActionResult Add()
        {
            AddTweetVM model = new AddTweetVM()
            {
                AppUsers = _appUserService.GetDefault(x => x.Role==Role.Member)
            };

            return View(model);
        }
        [HttpPost]
        public ActionResult Add(Tweet data, HttpPostedFileBase Image)
        {
            List<string> UploadedImagePaths = new List<string>();

            UploadedImagePaths = ImageUploader.UploadSingleImage(ImageUploader.OriginalProfileImagePath, Image, 1);

            data.ImagePath = UploadedImagePaths[0];

            if (data.ImagePath == "0" || data.ImagePath == "1" || data.ImagePath == "2")
            {
                data.ImagePath = ImageUploader.DefaultProfileImagePath;
                data.ImagePath = ImageUploader.DefaultXSmallProfileImage;
                data.ImagePath = ImageUploader.DefaulCruptedProfileImage;
            }
            else
            {
                data.ImagePath = UploadedImagePaths[1];
                data.ImagePath = UploadedImagePaths[2];
            }

            data.Status = Core.Enum.Status.Active;
            data.PublishDate = DateTime.Now;
            _tweetService.Add(data);
            return Redirect("/Admin/Tweet/List");
        }

        public ActionResult List()
        {
            List<Tweet> model = _tweetService.GetActive();
            return View(model);
        }
        public ActionResult Update(Guid id)
        {
            Tweet tweet = _tweetService.GetByID(id);
            UpdateTweetVM model = new UpdateTweetVM();
            model.Tweet.ID = tweet.ID;
            model.Tweet.ImagePath = tweet.ImagePath;
            model.Tweet.TweetContent = tweet.TweetContent;
            model.Tweet.PublishDate = DateTime.Now;
            List<AppUser> appusermodel = _appUserService.GetDefault(x => x.Role == Role.Member);
            model.AppUsers = appusermodel;
            return View(model);
        }
        [HttpPost]
        public ActionResult Update(TweetDTO data, HttpPostedFileBase Image)
        {

            List<string> UploadedImagePaths = new List<string>();

            UploadedImagePaths = ImageUploader.UploadSingleImage(ImageUploader.OriginalProfileImagePath, Image, 1);

            data.ImagePath = UploadedImagePaths[0];

            Tweet update = _tweetService.GetByID(data.ID);

            if (data.ImagePath == "0" || data.ImagePath == "1" || data.ImagePath == "2")
            {

                if (update.ImagePath == null || update.ImagePath == ImageUploader.DefaultProfileImagePath)
                {
                    update.ImagePath = ImageUploader.DefaultProfileImagePath;

                    update.ImagePath = ImageUploader.DefaultXSmallProfileImage;
                    update.ImagePath = ImageUploader.DefaulCruptedProfileImage;
                }
                else
                {
                    update.ImagePath = update.ImagePath;
                }

            }
            else
            {
                update.ImagePath = UploadedImagePaths[0];
                update.ImagePath = UploadedImagePaths[1];
                update.ImagePath = UploadedImagePaths[2];
            }
            Tweet tweet = _tweetService.GetByID(data.ID);
            tweet.PublishDate = data.PublishDate;
            tweet.TweetContent = data.TweetContent;
            tweet.AppUserID = data.AppUserID;
            tweet.Status = Core.Enum.Status.Updated;
            _tweetService.Update(tweet);

            return Redirect("/Admin/Tweet/List");

        }
        public RedirectResult Delete(Guid id)
        {
            _tweetService.Remove(id);
            return Redirect("/Admin/Tweet/List");
        }

    }
}<file_sep>using LittleBird.Model.Option;
using LittleBird.Service.Option;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LittleBird.UI.Areas.Member.Models.VM
{
    public class TweetDetailVM
    {
        public TweetDetailVM()
        {
            AppUsers = new List<AppUser>();
            Comments = new List<Comment>();
            Likes = new List<Like>();
            Tweets = new List<Tweet>();

            Like = new Like();
            Tweet = new Tweet();
            AppUser = new AppUser();
            Comment = new Comment();
        }
        //public int LikeCount { get; set; }
        //public int CommentCount { get; set; }

        public List<Comment> Comments {get; set;}
        public List<Like> Likes { get; set; }
        public List<AppUser> AppUsers { get; set; }
        public List<Tweet> Tweets { get; set; }

        public Like Like { get; set; }
        public Comment Comment { get; set; }
        public AppUser AppUser { get; set; }
        public Tweet Tweet { get; set; }

    }
}<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LittleBird.UI.Areas.Admin.Models.DTO
{
    public class TweetDTO
    {
        public Guid ID { get; set; }
        public string TweetContent { get; set; }
        public string ImagePath { get; set; }
        public DateTime? PublishDate { get; set; }

        public Guid AppUserID { get; set; }

    }
}<file_sep>using LittleBird.Model.Option;
using LittleBird.Service.BaseService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LittleBird.Service.Option
{
    public class TweetService : ServiceBase<Tweet>
    {
    }
}
<file_sep>using LittleBird.Core.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LittleBird.Model.Option
{
    public class Tweet : CoreEntity
    {
        public string TweetContent { get; set; }
        public string ImagePath { get; set; }
        public DateTime? PublishDate { get; set; }

        public Guid AppUserID { get; set; }
        public virtual AppUser AppUser { get; set; }

        public virtual List<Comment> Comments { get; set; }
        public virtual List<Like> Likes { get; set; }
    }
}
<file_sep>using LittleBird.Model.Option;
using LittleBird.Service.Option;
using LittleBird.UI.Areas.Member.Models.DTO;
using LittleBird.UI.Areas.Member.Models.VM;
using LittleBird.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace LittleBird.UI.Areas.Member.Controllers
{
    public class TweetController : Controller
    {
        AppUserService _appUserService;
        LikeService _likeService;
        TweetService _tweetService;
        CommentService _commentService;
        public TweetController()
        {
            _appUserService = new AppUserService();
            _likeService = new LikeService();
            _tweetService = new TweetService();
            _commentService = new CommentService();
        }
        public ActionResult Add()
        {
            Guid userID = _appUserService.FindByUserName(User.Identity.Name).ID;
            List<Tweet> model = _tweetService.GetDefault(x => x.AppUserID == userID && (x.Status == Core.Enum.Status.Active || x.Status == Core.Enum.Status.Updated));
            return View(model);

        }
        [HttpPost]
        public ActionResult Add(Tweet data, HttpPostedFileBase Image)
        {
            List<string> UploadedImagePaths = new List<string>();

            UploadedImagePaths = ImageUploader.UploadSingleImage(ImageUploader.OriginalProfileImagePath, Image, 1);

            data.ImagePath = UploadedImagePaths[0];

            if (data.ImagePath == "0" || data.ImagePath == "1" || data.ImagePath == "2")
            {
                data.ImagePath = ImageUploader.DefaultProfileImagePath;
                data.ImagePath = ImageUploader.DefaultXSmallProfileImage;
                data.ImagePath = ImageUploader.DefaulCruptedProfileImage;
            }
            else
            {
                data.ImagePath = UploadedImagePaths[1];
                data.ImagePath = UploadedImagePaths[2];
            }

            AppUser user = _appUserService.GetByDefault(x => x.UserName == User.Identity.Name);
            data.AppUserID = user.ID;
            data.PublishDate = DateTime.Now;

            _tweetService.Add(data);

            return Redirect("/Member/Tweet/Add");

        }
        [HttpPost]
        public ActionResult List()
        {
            Guid userID = _appUserService.FindByUserName(User.Identity.Name).ID;
            List<Tweet> model = _tweetService.GetDefault(x => x.AppUserID == userID && (x.Status == Core.Enum.Status.Active || x.Status == Core.Enum.Status.Updated));
            return View(model);
        }
        public ActionResult List2()
        {
            Guid userID = _appUserService.FindByUserName(User.Identity.Name).ID;
            List<Tweet> model = _tweetService.GetDefault(x => x.AppUserID == userID && (x.Status == Core.Enum.Status.Active || x.Status == Core.Enum.Status.Updated));
            return View(model);
        }
        public ActionResult Update(Guid id)
        {
            AddTweetDTO model = new AddTweetDTO();
            Tweet tweet = _tweetService.GetByID(id);

            model.ID= tweet.ID;

            model.TweetContent = tweet.TweetContent;
            model.PublishDate = DateTime.Now;
            model.ImagePath= tweet.ImagePath;

            return View(model);
        }
        [HttpPost]
        public ActionResult Update(AddTweetDTO data,HttpPostedFileBase Image)
        {

            List<string> UploadedImagePaths = new List<string>();

            UploadedImagePaths = ImageUploader.UploadSingleImage(ImageUploader.OriginalProfileImagePath, Image, 1);

            data.ImagePath = UploadedImagePaths[0];

            Tweet update = _tweetService.GetByID(data.ID);

            if (data.ImagePath == "0" || data.ImagePath == "1" || data.ImagePath == "2")
            {

                if (update.ImagePath == null || update.ImagePath == ImageUploader.DefaultProfileImagePath)
                {
                    update.ImagePath = ImageUploader.DefaultProfileImagePath;
                    update.ImagePath = ImageUploader.DefaultXSmallProfileImage;
                    update.ImagePath = ImageUploader.DefaulCruptedProfileImage;
                }
                else
                {
                    update.ImagePath = update.ImagePath;
                }

            }
            else
            {
                update.ImagePath = UploadedImagePaths[0];
                update.ImagePath = UploadedImagePaths[1];
                update.ImagePath = UploadedImagePaths[2];
            }
            Tweet tweet = _tweetService.GetByID(data.ID);

            tweet.TweetContent = data.TweetContent;
            tweet.PublishDate = data.PublishDate;
            tweet.Status = Core.Enum.Status.Updated;
            _tweetService.Update(tweet);
            return Redirect("/Member/Tweet/List");
        }
        public ActionResult Delete(Guid id)
        {
            _tweetService.Remove(id);
            return Redirect("/Member/Tweet/Add");
        }
        //public ActionResult Show(Guid id)
        //{
        //    TweetDetailVM model = new TweetDetailVM();

        //    model.Tweets = _tweetService.GetActive();

        //    foreach (var item in model.Tweets)
        //    {
        //        model.Comments = _commentService.GetDefault(x => x.TweetID == item.ID).OrderByDescending(x => x.CreatedDate).Take(10).ToList();

        //        model.LikeCount = _likeService.GetDefault(x => x.TweetID == item.ID).Count();
        //        model.CommentCount = _commentService.GetDefault(x => x.TweetID == item.ID).Count();
        //    }

        //    model.Tweet = _tweetService.GetByID(id);
        //    model.AppUser = _appUserService.GetByID(model.Tweet.AppUser.ID);
        //    model.Comments = _commentService.GetDefault(x => x.TweetID == id && (x.Status == Core.Enum.Status.Active || x.Status == Core.Enum.Status.Updated));
        //    model.LikeCount = _likeService.GetDefault(x => x.TweetID == id && (x.Status == Core.Enum.Status.Active || x.Status == Core.Enum.Status.Updated)).Count;
        //    model.CommentCount = _commentService.GetDefault(x => x.TweetID == id && (x.Status == Core.Enum.Status.Active || x.Status == Core.Enum.Status.Updated)).Count;
        //    model.Likes = _likeService.GetDefault(x => x.TweetID == id && (x.Status == Core.Enum.Status.Active || x.Status == Core.Enum.Status.Updated));

        //    return View(model);
       // }
    }

}<file_sep>using LittleBird.UI.Areas.Member.Models.DTO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LittleBird.UI.Areas.Member.Models.VM
{
    public class UpdateTweetVM
    {
        public UpdateTweetVM()
        {
            Tweet = new AddTweetDTO();
        }
        public AddTweetDTO Tweet { get; set; }
    }
}<file_sep>using LittleBird.Service.Option;
using LittleBird.UI.Areas.Member.Models.VM;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace LittleBird.UI.Areas.Member.Controllers
{
    public class HomeController : Controller
    {
        TweetService _tweetService;
        CommentService _commentService;
        LikeService _likeService;
        public HomeController()
        {
            _tweetService = new TweetService();
            _commentService = new CommentService();
            _likeService = new LikeService();
        }
        public ActionResult Index()
        {
            //TweetDetailVM model = new TweetDetailVM();

            //model.Tweets = _tweetService.GetActive();

            //foreach (var item in model.Tweets)
            //{
            //    model.Comments = _commentService.GetDefault(x => x.TweetID == item.ID).OrderByDescending(x => x.CreatedDate).Take(10).ToList();

            //    model.LikeCount = _likeService.GetDefault(x => x.TweetID == item.ID).Count();
            //    model.CommentCount = _commentService.GetDefault(x => x.TweetID == item.ID).Count();
            //}

            //return View(model);
            ////var model =_articleService.GetActive().OrderByDescending(x => x.CreatedDate).Take(5);
            return View();
        }
    }
}<file_sep>using LittleBird.Core.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LittleBird.Model.Option
{
    public enum Role
    {
        none=0,
        Admin=1,
        Member=2

    }

    public class AppUser : CoreEntity
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string PhoneNumber { get; set; }
        public string UserImage { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public string Bio { get; set; }
        public DateTime? Birthdate { get; set; }
        public string XSmallUserImage { get; set; }
        public string CruptedUserImage { get; set; }

        public string following { get; set; }
        public string followers { get; set; }
        public  Role Role { get;set; }

        public virtual List<Comment> Comments { get; set; }
        public virtual List<Like> Likes { get; set; }
        public virtual List<Tweet> Tweets { get; set; }

    }
}
<file_sep>using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LittleBird.UI.Areas.Member.Models.DTO
{
    public class AddTweetDTO
    {
        public Guid ID{ get; set; }
        public string TweetContent { get; set; }
        public string ImagePath { get; set; }
        public DateTime? PublishDate { get; set; }

        public Guid AppUserID { get; set; }
    }
}<file_sep>using LittleBird.Model.Option;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LittleBird.UI.Areas.Admin.Models.VM
{
    public class AddTweetVM
    {
        public AddTweetVM()
        {
            AppUsers = new List<AppUser>();
        }
        public List<AppUser> AppUsers { get; set; }
    }
}<file_sep>using LittleBird.Core.Map;
using LittleBird.Model.Option;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LittleBird.Map.Option
{
    public class LikeMap : CoreMap<Like>
    {
        public LikeMap()

        {
            ToTable("dbo.Likes");

            HasRequired(x => x.Tweet)
                .WithMany(x => x.Likes)
                .HasForeignKey(x => x.TweetID)
                .WillCascadeOnDelete(false);

            HasRequired(x => x.AppUser)
               .WithMany(x => x.Likes)
               .HasForeignKey(x => x.AppUserID)
               .WillCascadeOnDelete(false);

        }
    }
}
<file_sep>
@{
    ViewBag.Title = "List2";
    Layout = "~/Areas/Member/Views/Shared/denemeprofillayoutpage.cshtml";
}

languages: HTML+Razor C#

repo_language: HTML+Razor

repo_name: yunsemres/LittleBird-Tw


text:
<file_sep>package hem;

public class Frame {

	private  int firstRoll;
	private  int secondRoll;
	private int thirdRoll;
	private int score;
	public  boolean strike;
	private boolean spare;
	private  int bonus;

	public void setFirstRoll(int firstRoll) {
		this.firstRoll = firstRoll;
	}

	public void setSecondRoll(int secondRoll) {
		this.secondRoll = secondRoll;
	}

	public void setThirdRoll(int thirdRoll) {
		this.thirdRoll = thirdRoll;
	}

	public int getFirstRoll() {
		return firstRoll;
	}

	public int getSecondRoll() {
		return secondRoll;
	}

	public int getThirdRoll() {
		return thirdRoll;
	}
	public void setStrikeTrue() {
		strike = true;
	}

	public void setStrikeFalse() {
		strike = false;
	}

	public void spare() {
		spare = true;
	}

	public void setBonus(int bonus) {
		this.bonus = bonus;
	}

	public  int getScore() {
		return firstRoll + secondRoll + bonus;
	}
	public int getScoreForTenth() {
		return firstRoll + secondRoll + thirdRoll;
	}

	public void setScore(int score) {
		this.score = score;

	}

}

repo_language: Java

repo_name: Hersheychoc/Bowling


text:
<repo_name>radiojs/radio-ui<file_sep>/src/form/TextArea.js
import React from 'react';
import PropTypes from 'prop-types';

class TextArea extends React.Component {
  constructor(props) {
    super(props);

    this.handleChange = this.handleChange.bind(this);
    this.handleFocus = this.handleFocus.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.renderErrors = this.renderErrors.bind(this);
  }

  handleChange(e) {
    const { name, onChange } = this.props;
    const value = e.target.value;

    onChange(name, value);
  }

  handleFocus(e) {
    const { name, onFocus } = this.props;

    onFocus && onFocus(name);
  }

  handleBlur(e) {
    const { name, onBlur } = this.props;

    onBlur && onBlur(name);
  }

  renderErrors(errors) {
    if (!errors) return null;

    return (

        {errors.map((error, i) => (
          {error}
        ))}

    );
  }

  render() {
    const { label, name, value, errors } = this.props;
    const className = `form-control ${errors && errors.length > 0 ? 'error' : ''}`.trim();

    return (

        {label}
        <textarea
          className="TextArea"
          name={name}
          value={value}
          onChange={this.handleChange}
          onFocus={this.handleFocus}
          onBlur={this.handleBlur}
        />
        {this.renderErrors(errors)}

    );
  }
}

TextArea.propTypes = {
  label: PropTypes.string,
  name: PropTypes.string,
  value: PropTypes.string,
  onChange: PropTypes.func,
};

TextArea.defaultProps = {
  type: 'text',
};

export default TextArea;
<file_sep>/src/toolbar/_ToolBar.scss
.ToolBar {
    position: relative;

    &.top {
        border: none;
        // border-bottom: solid 1px $colorBorder;
        border-radius: none;
        box-shadow: 0 0 5px 1px lighten($color: $colorGray, $amount: 5%);

        .Separator {
            display: inline-block;
            width: 1px;
            height: calc(#{$buttonHeight} - 12px);
            margin: 6px 2px;
            border-left: solid 1px $colorLightGray;
            border-right: solid 1px $colorLightGray;
            background: $colorGray;
            vertical-align: bottom;
        }
    }

    &.float {
        position: absolute;
        top: $buttonHeight;
        left: -5px;
        border: solid 1px $colorBorder;
        // border-radius: $borderRadius;
        background: $colorBg;
        box-shadow: 0 3px 5px 1px lighten($color: $colorGray, $amount: 5%);
    }
}
<file_sep>/src/index.js
import Box from './box/Box';

import Dropdown from './dropdown/Dropdown';

import Button from './form/Button';
import CheckBox from './form/CheckBox';
import FileButton from './form/FileButton';
import Form from './form/Form';
import Input from './form/Input';
import Radio from './form/Radio';
import TextArea from './form/TextArea';

import Icon from './icon/Icon';

import List from './list/List';

import ErrorMessages from './message/ErrorMessages';

import Alert from './modal/Alert';
import Confirm from './modal/Confirm';
import Modal from './modal/Modal';

import Spinner from './spinner/Spinner';

import Tabs from './tab/Tabs';

import TextEditor from './text-editor/TextEditor';

import Toast from './toast/Toast';

import ToolBar from './toolbar/ToolBar';

import WysiwygEditor from './wysiwyg-editor/WysiwygEditor';

export {
  Alert,
  Box,
  Button,
  CheckBox,
  Confirm,
  Dropdown,
  ErrorMessages,
  FileButton,
  Form,
  Icon,
  Input,
  List,
  Modal,
  Radio,
  Spinner,
  Tabs,
  TextArea,
  TextEditor,
  Toast,
  ToolBar,
  WysiwygEditor,
};
<file_sep>/src/wysiwyg-editor/ImagePopup.js
import React from 'react';
import PropTypes from 'prop-types';
import validate from 'validate.js';

import FileButton from '../form/FileButton';
import Form from '../form/Form';
import Input from '../form/Input';
import Modal from '../modal/Modal';
import Tabs from '../tab/Tabs';

class ImagePopup extends React.Component {
  constructor(props) {
    super(props);

    const { value } = props;

    this.state = {
      activeTab: 'link',
      value: value || 'http://',
    };

    this.tabs = [{
      id: 'link',
      name: 'Link',
      active: true,
    }, {
      id: 'upload',
      name: 'Upload',
    }];

    this.handleChangeTab = this.handleChangeTab.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChangeTab(id) {
    this.setState({ activeTab: id });
  }

  handleChange(name, value) {
    this.setState({ value });
  }

  handleSubmit(e) {
    e.preventDefault();

    const { onSubmit } = this.props;
    const { value } = this.state;

    // validate URL
    const result = validate({ url: value }, { url: { url: true }});
    if (result && result.url) {
      this.setState({ error: result.url[0] });
    } else {
      onSubmit(value);
    }
  }

  render() {
    const { show, guideMessage, Upload, onClose } = this.props;
    const { activeTab, value } = this.state;

    return (
      <Modal
        show={show}
        title="Insert image"
        onClose={onClose}
      >
        <Tabs tabs={this.tabs} onChange={this.handleChangeTab} />
        <Tabs.Container id="link" active={activeTab === 'link'}>

{guideMessage}

          <Form onSubmit={this.handleSubmit}>
            <Input
              name="url"
              value={value}
              autoFocus
              onChange={this.handleChange}
            />
          </Form>
        </Tabs.Container>
        <Tabs.Container id="upload" active={activeTab === 'upload'}>
          <Upload />
        </Tabs.Container>
      </Modal>
    );
  }
}

ImagePopup.propTypes = {
  show: PropTypes.bool,
  value: PropTypes.string,
  guideMessage: PropTypes.string,
  url: PropTypes.string,
  uploadCallback: PropTypes.func,
  onSubmit: PropTypes.func.isRequired,
  onClose: PropTypes.func.isRequired,
};

ImagePopup.defaultProps = {
  show: false,
  guideMessage: 'Input Image URL',
};

export default ImagePopup;
<file_sep>/src/toast/Toast.js
import React from 'react';
import PropTypes from 'prop-types';

import Icon from '../icon/Icon';
import Button from '../form/Button';

class Toast extends React.Component {
  componentDidMount() {
    const { _id, closeAt, onClose } = this.props;

    setTimeout(() => {
      onClose(_id);
    }, closeAt);
  }

  render() {
    const { className, _id, message, icon, close, children, onClose } = this.props;

    return (
      <div className={`Toast ${className}`.trim()}>
        {icon && <Icon className="Icon" name={icon} />}
        {message &&
{message}
}
        {children}
        {close && (
          <Button className="icon close" onClick={() => { onClose(_id); }}>
            <Icon name="close" />
          </Button>
        )}

    )
  }
}

Toast.propTypes = {
  className: PropTypes.string,
  icon: PropTypes.string,
  message: PropTypes.string,
  close: PropTypes.bool,
  closeAt: PropTypes.number,
  onClose: PropTypes.func,
};

Toast.defaultProps = {
  className: '',
  close: true,
  closeAt: 3000,
};

export default Toast;
<file_sep>/src/form/CheckBox.js
/**
 * label, error message 등을 보여주는 기능을 포함한 input control 컴포넌트
 *
 */

import React from 'react';
import PropTypes from 'prop-types';

class CheckBox extends React.Component {

  constructor(props) {
    super(props);

    this.handleChange = this.handleChange.bind(this);
    this.renderErrors = this.renderErrors.bind(this);
    this.renderHelp = this.renderHelp.bind(this);
  }

  handleChange(e) {
    const { name, onChange } = this.props;
    const { checked } = e.target;

    onChange(name, checked);
  }

  renderErrors(errors) {
    return null;
  }

  renderHelp(help) {
    if (! help) return null;

    return (
      {help}
    );
  }

  render() {
    const {
      className,
      text,
      name,
      value,
      checked,
      errors,
      help,
    } = this.props;

    return (
      <div className={`form-control ${className}`.trim()}>
        <div className={`CheckBox ${errors ? 'error' : ''}`.trim()}>

            <input
              type="checkbox"
              name={name}
              value={value}
              checked={checked}
              onChange={this.handleChange}
            />
            {text}

        {this.renderErrors(errors)}
        {this.renderHelp(help)}

    );
  }
}

CheckBox.propTypes = {
  className: PropTypes.string,
  text: PropTypes.string,
  name: PropTypes.string.isRequired,
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
  checked: PropTypes.bool,
  onChange: PropTypes.func.isRequired,
  errors: PropTypes.arrayOf(PropTypes.string),
  help: PropTypes.string,
};

CheckBox.defaultProps = {
  className: '',
  text: '',
  value: '',
  checked: false,
  errors: null,
  help: null,
};

export default CheckBox;
<file_sep>/src/list/List.js
import React from 'react';
import PropTypes from 'prop-types';

class List extends React.Component {
  render() {
    const {
      className,
      listEmpty,
      listEmptyMessage,
      children,
    } = this.props;

    return (
      <ul className={`List ${className}`.trim()}>
        {listEmpty ? (

{listEmptyMessage}

        ) : children}

    );
  }
}

List.propTypes = {
  id: PropTypes.string,
  className: PropTypes.string,
  listEmpty: PropTypes.bool,
  listEmptyMessage: PropTypes.string,
};

List.defaultProps = {
  className: '',
  listEmpty: false,
  listEmptyMessage: 'List empty',
};

List.Item = ({ children }) => (
{children}
);

export default List;
<file_sep>/src/toolbar/ToolBar.js
import React from 'react';
import PropTypes from 'prop-types';

import Separator from './Separator';

const ToolBar = React.forwardRef((props, ref) => {
  const { className, show, children } = props;

  if (!show) return null;

  return (
    <div ref={ref} className={`ToolBar ${className}`.trim()}>{children}

  );
});

ToolBar.propTypes = {
  className: PropTypes.string,
  show: PropTypes.bool,
};

ToolBar.defaultProps = {
  className: '',
  show: false,
};

ToolBar.Separator = Separator;

export default ToolBar;
<file_sep>/src/form/Form.js
import React from 'react';
import PropTypes from 'prop-types';

class Form extends React.Component {
  render() {
    const { className, onSubmit, children } = this.props;

    return (
      <form
        className={`Form ${className}`.trim()}
        onSubmit={onSubmit}
      >
        {children}
      </form>
    );
  }
}

Form.propTypes = {
  className: PropTypes.string,
  onSubmit: PropTypes.func,
};

Form.defaultProps = {
  className: '',
};

export default Form;
<file_sep>/src/toast/_Toast.scss
.Toast {
    position: relative;
    width: 100%;
    max-width: $toastWidthMax;
    padding: 10px;
    border: solid 1px $colorBorder;
    border-radius: $borderRadius;
    box-sizing: border-box;
    box-shadow: 2px 2px 5px 1px $colorGray;

    .Icon {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        color: $colorBlue;
    }

    p {
        margin: 0 30px 0 0;
        line-height: 1.3em;
    }

    .Icon + p {
        margin-left: 30px;
    }

    .Button.close {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        right: 0;
        padding: 6px;
        border: none;
        background: transparent;
        font-size: 18px;
    }
}
<file_sep>/rollup.config.js
import nodeResolve from "rollup-plugin-node-resolve";
import babel from "rollup-plugin-babel";
import commonjs from "rollup-plugin-commonjs";
import replace from "rollup-plugin-replace";
import { sizeSnapshot } from "rollup-plugin-size-snapshot";
// import sass from "rollup-plugin-sass";
// import postcss from "rollup-plugin-postcss";
// import collectSass from "rollup-plugin-collect-sass";
import copy from 'rollup-plugin-copy-glob';

const globals = {
  react: "React",
  "react-dom": "ReactDOM"
};

const babelOptions = {
  exclude: /node_modules/,
  runtimeHelpers: true
};

const commonjsOptions = {
  include: /node_modules/,
  namedExports: {
    "prop-types": ["object", "oneOfType", "element", "bool", "func"],
    "draft-js/lib/Draft.js": [
      "AtomicBlockUtils",
      "CompositeDecorator",
      "ContentState",
      "convertFromHTML",
      "convertFromRaw",
      "convertToRaw",
      "DefaultDraftBlockRenderMap",
      "Editor",
      "EditorState",
      "RichUtils"
    ],
    "draft-js-export-html/lib/main.js": ["stateToHTML"],
  }
};

const input = "./src/index.js";

const output = {
  file: "dist/radio-ui.js",
  format: "cjs",
  globals,
};

export default {
  input,
  output,
  external: Object.keys(globals),
  plugins: [
    nodeResolve(),
    babel(babelOptions),
    commonjs(commonjsOptions),
    replace({ "process.env.NODE_ENV": JSON.stringify("development") }),
    sizeSnapshot(),
    copy([
      { files: 'src/**/*.scss', dest: 'dist/scss' }
    ])
  ],
  context: "this"
};
<file_sep>/src/form/Input.js
import React from 'react';
import PropTypes from 'prop-types';

class Input extends React.Component {
  constructor(props) {
    super(props);

    this.input = React.createRef();

    this.handleChange = this.handleChange.bind(this);
    this.handleFocus = this.handleFocus.bind(this);
    this.handleBlur = this.handleBlur.bind(this);
    this.renderErrors = this.renderErrors.bind(this);
  }

  componentDidMount() {
    if (this.props.autoFocus) {
      this.input.current.focus();
    }
  }

  handleChange(e) {
    const { name, onChange } = this.props;
    const value = e.target.value;

    onChange && onChange(name, value);
  }

  handleFocus(e) {
    const { name, onFocus } = this.props;

    onFocus && onFocus(name);
  }

  handleBlur(e) {
    const { name, onBlur } = this.props;

    onBlur && onBlur(name);
  }

  renderErrors(errors) {
    if (!errors) return null;

    return (

        {errors.map((error, i) => (
          {error}
        ))}

    );
  }

  render() {
    const { label, type, name, value, placeholder, errors } = this.props;
    const className = `form-control ${errors && errors.length > 0 ? 'error' : ''}`.trim();

    return (

        {label && ({label})}
        <input
          ref={this.input}
          className="Input"
          type={type}
          name={name}
          value={value}
          placeholder={placeholder}
          onChange={this.handleChange}
          onFocus={this.handleFocus}
          onBlur={this.handleBlur}
        />
        {this.renderErrors(errors)}

    );
  }
}

Input.propTypes = {
  type: PropTypes.string,
  label: PropTypes.string,
  name: PropTypes.string,
  value: PropTypes.string,
  placeholder: PropTypes.string,
  errors: PropTypes.arrayOf(PropTypes.string),
  autoFocus: PropTypes.bool,
  onChange: PropTypes.func,
  onFocus: PropTypes.func,
  onBlur: PropTypes.func,
};

Input.defaultProps = {
  type: 'text',
  placeholder: '',
  autoFocus: false,
};

export default Input;
<file_sep>/src/box/_Box.scss
@import '../scss/variables';

.Box {
  width: 50px;
  height: 50px;
  border: solid 1px $colorBorder;

  &.round {
    border-radius: 50%;
  }

  &.white {
    background: $colorWhite;
  }

  &.black {
    background: $colorBlack;
  }

  &.gray {
    background: $colorGray;
  }

  &.red {
    background: $colorRed;
  }

  &.green {
    background: $colorGreen;
  }

  &.blue {
    background: $colorBlue;
  }

  &.primary {
    background: $colorPrimary;
  }

  &.disabled {
    background: $colorDisabled;
  }
}<file_sep>/src/spinner/Spinner.js
import React from 'react';
import PropTypes from 'prop-types';
import MoonLoader from 'react-spinners/MoonLoader';
import BarLoader from 'react-spinners/BarLoader';

const spinners = {
  bar: (

      <BarLoader size={100} sizeUnit={'%'} />

  ),

  moon: (

      <MoonLoader size={30} />

  ),
};

class Spinner extends React.Component {
  render() {
    return spinners[this.props.style];
  }
}

Spinner.propTypes = {
  style: PropTypes.oneOf(['bar', 'moon']),
};

Spinner.defaultProps = {
  style: 'moon',
};

export default Spinner;
<file_sep>/src/form/_Button.scss
.Button, .btn {
  position: relative;
  display: inline-block;
  box-sizing: border-box;
  height: $buttonHeight;
  padding: 10px 20px;
  border: solid 1px $colorBorder;
  border-radius: $borderRadius;
  background: $colorWhite;
  color: $colorButton;
  font-size: 1em;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;

  .fa-spin {
    display: none;
    margin-right: 10px;
  }

  &.large {
    height: $buttonHeightLarge;
    padding: 12px 24px;
    font-size: 1.2em;
  }

  &.small {
    height: $buttonHeightSmall;
    padding: 8px 16px;
    font-size: 0.8em;
  }

  &:hover {
    background: $colorGray;
    color: $colorButtonHover;
  }

  &:focus {
    outline: 0;
  }

  &:disabled {
    border-color: lighten($color: $colorGray, $amount: 10%);
    background: lighten($color: $colorGray, $amount: 10%);
    color: $colorLightGray;

    .fa-spin {
      display: inline-block;
    }
  }

  &.primary {
    border-color: $colorPrimary;
    background: $colorPrimary;
    color: $colorWhite;

    &:disabled {
      border-color: lighten($color: $colorPrimary, $amount: 10%);
      background: lighten($color: $colorPrimary, $amount: 10%);
      color: $colorGray;

      .fa-spin {
        display: inline-block;
      }
    }
  }

  &.block {
    width: 100%;
  }
}
<file_sep>/src/text-editor/ContentEditable.js
import React from 'react';
import PropTypes from 'prop-types';

class ContentEditable extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      html: '',
    };

    this.emitChange = this.emitChange.bind(this);
  }

  componentDidMount() {
    const { value, autoFocus, onSelectChange } = this.props;
    this.setState({ html: value });

    document.addEventListener('selectionchange', onSelectChange);

    if (autoFocus && this.element) {
      this.element.focus();
    } else {
      this.element.innerHTML = value;
    }
  }

  componentDidUpdate() {
    if (this.element && this.props.html !== this.element.innerHTML) {
      // Perhaps React (whose VDOM gets outdated because we often prevent
      // rerendering) did not update the DOM. So we update it manually now.
      this.element.innerHTML = this.props.html;
    }
  }

  componentWillUnmount() {
    document.removeEventListener('selectionchange', this.props.onSelectChange);
  }

/*
  componentWillReceiveProps(nextProps) {
    const { value } = nextProps;
    this.setState({ html: value });
  }

  shouldComponentUpdate(nextProps) {
    let { props, element } = this;

    console.log('props', props);
    console.log('nextProps', nextProps);
    // We need not rerender if the change of props simply reflects the user's edits.
    // Rerendering in this case would make the cursor/caret jump

    // Rerender if there is no element yet... (somehow?)
    if (!element) {
      return true;
    }

    // ...or if html really changed... (programmatically, not by user edit)
    if (nextProps.html !== element.innerHTML && nextProps.html !== props.html) {
      return true;
    }

    let optional = ['style', 'className', 'disabled', 'tagName'];

    // Handle additional properties
    return optional.some(name => props[name] !== nextProps[name]);
  }
*/
  emitChange(e) {
    if (!this.element) return;

    const { name, onChange } = this.props;
    let html = this.element.innerHTML;

    if (!html) {
      html = '

';
      this.element.innerHTML = html;
      /*
      const range = document.createRange();
      const sel = window.getSelection();
      range.setStart(this.element.childNodes[0], 1);
      range.collapse(true);
      sel.removeAllRanges();
      sel.addRange(range);
      */
    }

    if (onChange && html !== this.lastHtml) {
      e.target = { name, value: html };
      onChange(e);
    }

    this.lastHtml = html;
  }

  render() {
    const { tagName, className, disabled, onBlur, onKeyDown } = this.props;
    const { html } = this.state;

    return React.createElement(tagName, {
      ref: (c) => { this.element = c; },
      className: `ContentEditable ${className}`.trim(),
      dangerouslySetInnerHTML: { __html: html },
      contentEditable: !disabled,
      onInput: this.emitChange,
      onBlur: onBlur || this.emitChange,
      onKeyDown: onKeyDown || this.emitChange,
    });
  }
}

ContentEditable.propTypes = {
  name: PropTypes.string,
  tagName: PropTypes.string,
  className: PropTypes.string,
  disabled: PropTypes.bool,
  autoFocus: PropTypes.bool,
  placeholder: PropTypes.string,
  onBlur: PropTypes.func,
  onKeyDown: PropTypes.func,
  onSelectChange: PropTypes.func,
};

ContentEditable.defaultProps = {
  tagName: 'div',
  className: '',
  disabled: false,
  autoFocus: false,
  placeholder: '',
  onSelectChange: () => {},
};

export default ContentEditable;
<file_sep>/src/icon/Icon.js
import React from 'react';
import PropTypes from 'prop-types';
import {
  MdClear,
  MdCode,
  MdFormatBold,
  MdFormatItalic,
  MdFormatListBulleted,
  MdFormatListNumbered,
  MdFormatQuote,
  MdFormatUnderlined,
  MdImage,
  MdLink,
  MdTextFields,
  MdVideocam,
} from 'react-icons/md';

class Icon extends React.Component {
  constructor(props) {
    super(props);

    if (props.icons) {
      Icon.icons = Object.assign({}, Icon.icons, props.icons);
    }
  }

  render() {
    const { name } = this.props;
    const Component = Icon.icons[name];

    return Component ? <Component {...this.props} /> : null;
  }
}

Icon.icons = {
  close: MdClear,
  code: MdCode,
  blockquote: MdFormatQuote,
  bold: MdFormatBold,
  italic: MdFormatItalic,
  orderedlist: MdFormatListNumbered,
  underlined: MdFormatUnderlined,
  unorderedlist: MdFormatListBulleted,
  fontsize: MdTextFields,
  link: MdLink,
  image: MdImage,
  video: MdVideocam,
};

Icon.propTypes = {
  icons: PropTypes.object,
};

export default Icon;
<file_sep>/src/tab/Tabs.js
import React from 'react';
import PropTypes from 'prop-types';

import Tab from './Tab';
import TabContainer from './TabContainer';

class Tabs extends React.Component {
  constructor(props) {
    super(props);

    const { tabs } = props;
    this.state = { tabs };

    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(e, id) {
    const { onChange } = this.props;
    const { tabs } = this.state;

    const updated = tabs.map((doc) => {
      if (doc.id === id) {
        doc.active = true;
      } else {
        doc.active = false;
      }
      return doc;
    });

    this.setState({ tabs: updated });
    onChange && onChange(id);
  }

  render() {
    const { tabs } = this.state;

    return (

        {tabs.map(item => (
          <Tab key={item.id} {...item} onClick={this.handleClick} />
        ))}

    );
  }
}

Tabs.propTypes = {
  tabs: PropTypes.arrayOf(PropTypes.object),
};

Tabs.defaultProps = {
  tabs: [],
};

Tabs.Container = TabContainer;

export default Tabs;
<file_sep>/src/modal/Alert.js
import React from 'react';
import PropTypes from 'prop-types';
import ReactModal from 'react-modal';

import Button from '../form/Button';

class Alert extends React.Component {
  render() {
    const { show, message, buttonText, appElementId, onClose } = this.props;

    if (!show) return null;

    return (
      <ReactModal
        className="Modal"
        overlayClassName="ModalOverlay"
        isOpen={show}
        appElement={document.getElementById(appElementId)}
      >

{message}

          <Button className="primary" onClick={onClose}>{buttonText}</Button>

      </ReactModal>
    )
  }
}

Alert.propTypes = {
  show: PropTypes.bool,
  message: PropTypes.string,
  buttonText: PropTypes.string,
  appElementId: PropTypes.string,
  onClose: PropTypes.func,
};

Alert.defaultProps = {
  show: true,
  buttonText: 'Ok',
  appElementId: 'root',
};

export default Alert;
<file_sep>/src/list/_List.scss
.List {
  margin: 10px 0;
  padding: 0;
  list-style: none;

  > li {
    position: relative;
    margin: 0;
    padding: 0;
    line-height: 1.5em;

    .right {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      right: 0;
    }
  }

  li + li {
    margin-top: 10px;
  }

  &.horizontal {
    > li {
      display: inline-block;
    }
  }
}
<file_sep>/src/form/Button.js
import React from 'react';
import PropTypes from 'prop-types';

class Button extends React.Component {
  render() {
    const { type, className, disabled, onClick, children } = this.props;

    return (
      <button
        type={type}
        className={`Button ${className}`.trim()}
        disabled={disabled}
        onClick={onClick}
      >
        {children}
      </button>
    );
  }
}

Button.propTypes = {
  type: PropTypes.string,
  className: PropTypes.string,
  disabled: PropTypes.bool,
  onClick: PropTypes.func,
};

Button.defaultProps = {
  type: 'button',
  className: '',
  disabled: false,
};

export default Button;
<file_sep>/README.md
# radio-ui

React UI components

## Features

- React Components
- SCSS supported

## Components

- Box
- Form: Button, CheckBox, Form, Input, Radio, TextArea
- Icon
- List
- Message: ErrorMessages
- Modal: Alert, Confirm, Modal
- Spinner
- TextEditor
- Toast
- ToolBar
- WysiwygEditor

<file_sep>/src/wysiwyg-editor/WysiwygEditor.js
import React from 'react';
import PropTypes from 'prop-types';
import {
  AtomicBlockUtils,
  convertFromRaw,
  convertToRaw,
  DefaultDraftBlockRenderMap,
  Editor,
  EditorState,
  RichUtils,
} from 'draft-js';
import Immutable from 'immutable';
import _ from 'lodash';

import EntityMappers from './EntityMappers';
import ActionBar from './ActionBar';
import LinkPopup from './LinkPopup';
import VideoPopup from './VideoPopup';

class WysiwygEditor extends React.Component {
  constructor(props) {
    super(props);

    this.editor = React.createRef();

    // set initial values
    const { name, value } = props;
    const rawValue = !_.isEmpty(value) ? value : {
      blocks: [{
        text: '',
        type: 'header-one',
      }, {
        text: '',
        type: 'unstyled',
      }],
      entityMap: [],
    };

    // initial format is the typeof { blocks: [...], entityMap: [...] }
    // it should be morphed to the type of { blocks: [...], entityMap: { 0: {}, 1: {}, ...}};
    // and the field '__typename' should be deleted
    const entityMapObject = {};
    if (rawValue.entityMap && rawValue.entityMap.length > 0) {
      rawValue.entityMap.forEach((doc, i) => {
        if (doc && doc.data) {
          delete doc.data.__typename;
          entityMapObject[i] = doc;
        }
      });
    }
    rawValue.entityMap = entityMapObject;

    const contentState = convertFromRaw(rawValue);
    const editorState = EditorState.createWithContent(contentState, EntityMappers);

    this.state = {
      name,
      editorState,
      showLinkPopup: false,
      showImagePopup: false,
      showVideoPopup: false,
    };

    this.bufferHandle = null;
    this.convertContent = this.convertContent.bind(this);
    this.toggleLinkPopup = this.toggleLinkPopup.bind(this);
    this.toggleImagePopup = this.toggleImagePopup.bind(this);
    this.toggleVideoPopup = this.toggleVideoPopup.bind(this);
    this.handleKeyCommand = this.handleKeyCommand.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleInlineStyle = this.handleInlineStyle.bind(this);
    this.handleBlockType = this.handleBlockType.bind(this);
    this.handleLinkUrl = this.handleLinkUrl.bind(this);
    this.handleImageUrl = this.handleImageUrl.bind(this);
    this.handleVideoUrl = this.handleVideoUrl.bind(this);
  }

  componentDidMount() {
    if (this.props.autoFocus) {
      this.editor.current.focus();
    }
  }

  componentWillUnmount() {
    if (this.bufferHandle) {
      clearTimeout(this.bufferHandle);
      this.bufferHandle = null;
    }
  }

  convertContent(rawContent) {
    const { editorState } = this.state;

    const converted = rawContent || convertToRaw(editorState.getCurrentContent());
    converted.entityMap = Object.keys(converted.entityMap).sort().map(i => converted.entityMap[i]);

    return {
      title: converted.blocks[0] && converted.blocks[0].text,
      content: converted,
    };
  }

  toggleLinkPopup() {
    const { editorState } = this.state;
    const selection = editorState.getSelection();

    // get Link URL on the current selection or on the caret position
    if (!selection.isCollapsed()) {
      const contentState = editorState.getCurrentContent();
      const startKey = editorState.getSelection().getStartKey();
      const startOffset = editorState.getSelection().getStartOffset();
      const blockWithLinkAtBeginning = contentState.getBlockForKey(startKey);
      const linkKey = blockWithLinkAtBeginning.getEntityAt(startOffset);
      if (linkKey) {
        const data = contentState.getEntity(linkKey).getData();
        this.setState({
          link: data,
          showLinkPopup: !this.state.showLinkPopup,
        });
      } else {
        this.setState({
          link: null,
          showLinkPopup: !this.state.showLinkPopup,
        });
      }
    }
  }

  toggleImagePopup() {
    this.setState({
      showImagePopup: !this.state.showImagePopup,
    });
  }

  toggleVideoPopup() {
    this.setState({
      showVideoPopup: !this.state.showVideoPopup,
    });
  }

  handleKeyCommand(command, editorState) {
    const newState = RichUtils.handleKeyCommand(editorState, command);
    if (newState) {
      this.handleChange(newState);
      return 'handled';
    }
    return 'not-handled';
  }

  handleChange(state) {
    const { onChange } = this.props;
    const { editorState, name } = this.state;

    const rawContentBefore = convertToRaw(editorState.getCurrentContent());
    const rawContent = convertToRaw(state.getCurrentContent());

    this.setState({ editorState: state });

    // do nothing if the content has not changed
    if (JSON.stringify(rawContentBefore) === JSON.stringify(rawContent)) return;

    const { bufferDuration } = this.props;
    if (bufferDuration > 0) {
      if (this.bufferHandle) {
        clearTimeout(this.bufferHandle);
        this.bufferHandle = null;
      }

      this.bufferHandle = setTimeout(() => {
        onChange(name, this.convertContent(rawContent));
      }, this.props.bufferDuration);
    } else {
      onChange(name, this.convertContent(rawContent));
    }
  }

  /**
   * 선택된 영역을 지정한 스타일로 변경한다.
   *
   * @param {*} style : 'BOLD', 'ITALIC', 'UNDERLINE'
   */
  handleInlineStyle(e, style) {
    this.handleChange(RichUtils.toggleInlineStyle(this.state.editorState, style));
  }

  handleBlockType(e, type) {
    this.handleChange(RichUtils.toggleBlockType(this.state.editorState, type));
  }

  /**
   * 링크를 삽입하거나 기존에 존재하는 링크를 삭제한다.
   * 삽입하는 경우는, url 값을 받아서 선택된 영역을  태그로 감싼다.
   *
   * @param {*} url
   */
  handleLinkUrl(url, target = '_self') {
    const { editorState } = this.state;
    const contentState = editorState.getCurrentContent();

    if (url) {
      const contentStateWithEntity = contentState.createEntity(
        'LINK',
        'MUTABLE',
        { url, target },
      );
      const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
      const newEditorState = EditorState.set(editorState, {
        currentContent: contentStateWithEntity,
      });
      const editorStateUpdated = RichUtils.toggleLink(
        newEditorState,
        newEditorState.getSelection(),
        entityKey
      );

      this.setState({
        editorState: editorStateUpdated,
        link: null,
        showLinkPopup: false,
      }, () => {
        setTimeout(() => this.editor.current.focus(), 0);
      });
    }
  }

  handleImageUrl(e) {
    this.props.onImagePopup((url, className = '') => {
      const { editorState } = this.state;
      const contentState = editorState.getCurrentContent();

      const contentStateWithEntity = contentState.createEntity(
        'IMAGE',
        'MUTABLE',
        { url, className },
      );
      const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
      const newEditorState = EditorState.set(editorState, {
        currentContent: contentStateWithEntity,
      });
      const editorStateUpdated = AtomicBlockUtils.insertAtomicBlock(
        newEditorState,
        entityKey,
        ' ',
      );

      this.setState({
        editorState: editorStateUpdated,
        showImagePopup: false,
      }, () => {
        setTimeout(() => this.editor.current.focus(), 0);
      });
    });
  }

  handleVideoUrl(url, className = '') {
    const { editorState } = this.state;
    const contentState = editorState.getCurrentContent();

    if (url) {
      const contentStateWithEntity = contentState.createEntity(
        'VIDEO',
        'MUTABLE',
        { url, className },
      );
      const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
      const newEditorState = EditorState.set(editorState, {
        currentContent: contentStateWithEntity,
      });
      const editorStateUpdated = AtomicBlockUtils.insertAtomicBlock(
        newEditorState,
        entityKey,
        ' ',
      );

      this.setState({
        editorState: editorStateUpdated,
        videoUrl: null,
        showVideoPopup: false,
      }, () => {
        setTimeout(() => this.editor.current.focus(), 0);
      });
    } else {

    }
  }

  render() {
    const { placeholder } = this.props;
    const {
      editorState,
      link,
      videoUrl,
      showLinkPopup,
      showVideoPopup,
    } = this.state;

    const blockRenderMap = Immutable.Map({
      'unstyled': {
        element: 'div',
        aliasedElements: ['p'],
        className: 'unstyled',
      },
     });

    return (

        <Editor
          ref={this.editor}
          editorState={editorState}
          placeholder={placeholder}
          blockRenderMap={DefaultDraftBlockRenderMap.merge(blockRenderMap)}
          handleKeyCommand={this.handleKeyCommand}
          onChange={this.handleChange}
        />
        <ActionBar
          onInlineStyle={this.handleInlineStyle}
          onBlockType={this.handleBlockType}
          onLinkPopup={this.toggleLinkPopup}
          onImagePopup={this.handleImageUrl}
          onVideoPopup={this.toggleVideoPopup}
        />
        <LinkPopup
          key={showLinkPopup}
          show={showLinkPopup}
          value={link}
          onSubmit={this.handleLinkUrl}
          onClose={this.toggleLinkPopup}
        />
        <VideoPopup
          show={showVideoPopup}
          value={videoUrl}
          onSubmit={this.handleVideoUrl}
          onClose={this.toggleVideoPopup}
        />

    );
  }
}

WysiwygEditor.propTypes = {
  name: PropTypes.string,
  value: PropTypes.object,
  placeholder: PropTypes.string,
  autoFocus: PropTypes.bool,
  bufferDuration: PropTypes.number,
  onImagePopup: PropTypes.func,
};

WysiwygEditor.defaultProps = {
  name: 'editor',
  value: {},
  autoFocus: false,
  bufferDuration: 500,
};

export default WysiwygEditor;
<file_sep>/src/wysiwyg-editor/_WysiwygEditor.scss
.WysiwygEditor {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    overflow: hidden;
    background: transparent;

    .DraftEditor-root {
        position: absolute;
        top: calc(#{$toolbarHeight} + 2px);
        right: 0;
        bottom: 0;
        left: 0;
        overflow: auto;
        padding: 10px;

        h1, h2, h3, h4, h5, h6, p, .public-DraftStyleDefault-block {
            margin: 0;
            line-height: 2em;
        }

        h1 {
            font-size: 2em;
            font-weight: bold;
        }
        h2 {
            font-size: 1.5em;
            font-weight: bold;
        }
        h3 {
            font-size: 1.17em;
            font-weight: bold;
        }
        h4 {
            font-size: 1em;
            font-weight: bold;
        }
        h5 {
            font-size: .83em;
            font-weight: bold;
        }
        h6 {
            font-size: .67em;
            font-weight: bold;
        }
        p, .public-DraftStyleDefault-block {
            font-size: 1em;
        }
        img {
            width: 100%;
        }
    };

    .public-DraftEditorPlaceholder-root {
        position: absolute;
        top: 0;
        left: 0;
        color: $colorGray;
        font-size: 1.6em;
        pointer-events: none;
    }

    .ActionBar {
        height: $toolbarHeight;
        margin-top: 1px;

        .FontBar {
            background: $colorWhite;

            .link {
                padding: 5px;
                font-size: initial;
            }

            h1, h2, h3, h4, h5, h6, p {
                margin: 0;
                text-align: left;
            }

            .link + .link {
                border-top: solid 1px $colorBorder;
            }
        }
    }

}
<file_sep>/src/form/_Form.scss
.Form {
  margin: 20px 0;

  .form-control {
    position: relative;

    > label {
      display: block;
      color: $colorTextLabel;
      font-size: 0.9em;
      line-height: 1.5;
    }
  }

  .form-control + .form-control {
    margin-top: 15px;
  }

  .form-actions {
    position: relative;
    margin-top: 20px;

    .right {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      right: 0;
      margin-top: 20px;
    }
  }

  &.no-border {
    margin: 0;

    .ContentEditable {
      border: none;
    }
  }
}
<file_sep>/src/tab/Tab.js
import React from 'react';
import PropTypes from 'prop-types';

class Tab extends React.Component {
  render() {
    const { id, name, active, onClick } = this.props;

    return (
      <div
        className={`Tab ${active ? 'active' : ''}`.trim()}
        onClick={(e) => { onClick(e, id); }}
      >{name}

    );
  }
}

Tab.propTypes = {
  id: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  active: PropTypes.bool,
  onClick: PropTypes.func,
};

Tab.defaultProps = {
  active: false,
};

export default Tab;
<file_sep>/src/modal/Confirm.js
import React from 'react';
import PropTypes from 'prop-types';
import ReactModal from 'react-modal';

import Button from '../form/Button';

class Confirm extends React.Component {

  constructor(props) {
    super(props);

    this.handleYes = this.handleYes.bind(this);
    this.handleNo = this.handleNo.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }

  handleYes() {
    const { onAnswer } = this.props;
    onAnswer && onAnswer(true);
  }

  handleNo() {
    const { onAnswer } = this.props;
    onAnswer && onAnswer(false);
  }

  handleClick(e, i) {
    e.preventDefault();

    const { onAnswer } = this.props;
    onAnswer && onAnswer(i);
  }

  render() {
    const { show, message, buttons, appElementId } = this.props;

    if (!show) return null;

    return (
      <ReactModal
        className="Modal"
        overlayClassName="ModalOverlay"
        isOpen={show}
        appElement={document.getElementById(appElementId)}
      >

{message}

          {buttons.map((button, i) => (
            <Button
              key={i}
              className={button.style}
              onClick={ (e) => { this.handleClick(e, i); } }
            >{button.title}</Button>
          ))}

      </ReactModal>
    )
  }
}

Confirm.propTypes = {
  show: PropTypes.bool,
  message: PropTypes.string,
  buttons: PropTypes.arrayOf(PropTypes.object),
  appElementId: PropTypes.string,
};

Confirm.defaultProps = {
  show: true,
  buttons: [{
    title: 'No',
  }, {
    title: 'Yes',
    style: 'primary',
  }],
  appElementId: 'root',
};

export default Confirm;
<file_sep>/src/message/_ErrorMessages.scss
.ErrorMessages {
  margin: 10px 0;
  padding: 10px;
  background: $colorErrorBg;
  border: solid 1px $colorBorder;
  color: $colorRed;
  list-style: none;
}
<file_sep>/src/dropdown/Dropdown.js
import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';

class Dropdown extends React.Component {
  constructor(props) {
    super(props);

    this.handleClick = this.handleClick.bind(this);
  }

  handleClick(e, id) {
    e.preventDefaul();
    this.props.onClick(id);
  }

  render() {
    const { menu } = this.props;

    return (

          {menu && menu.map(item => (

 { this.handleClick(item.id); }}
            >{item.text}

          ))}

    );
  }
}

Dropdown.propTypes = {
  menu: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.string,
    text: PropTypes.string,
    link: PropTypes.string,
  })),
};

Dropdown.defaultProps = {
  menu: [],
};

export default Dropdown;
<file_sep>/src/wysiwyg-editor/VideoPopup.js
import React from 'react';
import PropTypes from 'prop-types';
import validate from 'validate.js';

import Form from '../form/Form';
import Input from '../form/Input';
import Modal from '../modal/Modal';

class VideoPopup extends React.Component {
  constructor(props) {
    super(props);

    const { value } = props;

    this.state = {
      value: value || 'http://',
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(name, value) {
    this.setState({ value });
  }

  handleSubmit(e) {
    e.preventDefault();

    const { onSubmit } = this.props;
    const { value } = this.state;

    // validate URL
    const result = validate({ url: value }, { url: { url: true }});
    if (result && result.url) {
      this.setState({ error: result.url[0] });
    } else {
      onSubmit(value);
    }
  }

  render() {
    const { show, guideMessage, onClose } = this.props;
    const { value } = this.state;

    return (
      <Modal
        show={show}
        onClose={onClose}
      >

{guideMessage}

        <Form onSubmit={this.handleSubmit}>
          <Input
            name="url"
            value={value}
            autoFocus
            onChange={this.handleChange}
          />
        </Form>
      </Modal>
    );
  }
}

VideoPopup.propTypes = {
  show: PropTypes.bool,
  value: PropTypes.string,
  guideMessage: PropTypes.string,
  url: PropTypes.string,
  onSubmit: PropTypes.func.isRequired,
  onClose: PropTypes.func.isRequired,
};

VideoPopup.defaultProps = {
  show: false,
  guideMessage: 'Input Video URL',
};

export default VideoPopup;

branch_name: refs/heads/devel

languages: Markdown SCSS JavaScript

repo_name: radiojs/radio-ui